1 /**
2  * PNG file format image loader/exporter
3  * 
4  * License:
5  *              Copyright Devisualization (Richard Andrew Cattermole) 2014 - 2017.
6  *     Distributed under the Boost Software License, Version 1.0.
7  *        (See accompanying file LICENSE_1_0.txt or copy at
8  *              http://www.boost.org/LICENSE_1_0.txt)
9  */
10 module devisualization.image.fileformats.png;
11 import devisualization.image.fileformats.defs : HeadersOnly, ImageNotLoadableException, ImageNotExportableException;
12 import devisualization.image.interfaces;
13 import devisualization.image.primitives : isImage, ImageColor;
14 import devisualization.image.storage.base : ImageStorageHorizontal;
15 import devisualization.util.core.memory.managed;
16 import stdx.allocator : IAllocator, theAllocator, makeArray, make, expandArray, dispose;
17 import std.experimental.color : isColor, RGB8, RGBA8, convertColor;
18 import std.experimental.color.rgb : RGB;
19 import std.range : isInputRange, ElementType;
20 import std.datetime : DateTime;
21 import std.traits : isPointer;
22 import std.typecons : tuple;
23 
24 ///
25 alias HeadersOnlyPNGFileFormat = PNGFileFormat!HeadersOnly;
26 
27 /**
28  * PNG file format representation
29  * 
30  * Does not actually support color correction/alteration for the chunks: cHRM, sRGB, iCCP and gAMA.
31  * Use them once you have already performed the alterations upon IDAT and respective data.
32  * 
33  * FIXME:
34  *      Reliance on e.g. GC/processAllocator for when compressing/decompressing via zlib.
35  *      Exporters use of filters 2, 3 and 4. Creates artifacts.
36  */
37 struct PNGFileFormat(Color) if (isColor!Color || is(Color == HeadersOnly)) {
38     import std.bitmanip : bigEndianToNative, nativeToBigEndian;
39 	import containers.hashmap;
40 	import containers.dynamicarray;
41 
42     ///
43     IHDR_Chunk IHDR;
44     
45     ///
46     PLTE_Chunk* PLTE;
47     
48     ///
49     tRNS_Chunk* tRNS;
50     
51     ///
52     gAMA_Chunk* gAMA;
53     
54     ///
55     cHRM_Chunk* cHRM;
56     
57     ///
58     sRGB_Chunk* sRGB;
59     
60     ///
61     iCCP_Chunk* iCCP;
62     
63     /// tEXt values are really latin-1 but treated as UTF-8 in D code, may originate from iEXt
64 	HashMap!(PngTextKeywords, string, IAllocator)* tEXt;
65     /// zEXt values are really latin-1 but treated as UTF-8 in D code, may originate from iEXt
66 	HashMap!(PngTextKeywords, string, IAllocator)* zEXt;
67     
68     ///
69     bKGD_Chunk* bKGD;
70     
71     ///
72     pPHs_Chunk* pPHs;
73     
74     ///
75     sBIT_Chunk* sBIT;
76     
77     ///
78 	DynamicArray!(sPLT_Chunk, IAllocator)* sPLT;
79     ///
80 	DynamicArray!(ushort, IAllocator)* hIST;
81 
82     ///
83     DateTime* tIME;
84 
85 	// we can't copy because of ImageStorage type probably won't be able to be
86 	@disable
87 	this(this);
88 
89     static if (!is(Color == HeadersOnly)) {
90         /// Only available when Color is specified as not HeadersOnly
91         ImageStorage!Color value;
92         alias value this;
93         
94         ///
95         managed!(ubyte[]) toBytes() {
96             return performExport();
97         }
98     }
99     
100     string toString() {
101         import std.conv : text;
102         char[] ret;
103         
104         void addText(string[] toAdd...) {
105             import std.algorithm : sum, map;
106             size_t len = ret.length;
107 
108             if (len == 0)
109                 ret = allocator.makeArray!char(toAdd.map!`a.length`.sum);
110             else
111                 allocator.expandArray(ret, toAdd.map!`a.length`.sum);
112             
113             foreach(v; toAdd) {
114                 ret[len .. len + v.length] = v[];
115                 len += v.length;
116             }
117         }
118         
119         addText("PNGFileFormat!", Color.stringof, " [\n\tChunks: [\n");
120         
121         addText("\t\t", IHDR.text, ",\n");
122         
123         if (tEXt.keys.length > 0) {
124 			addText("\t\ttEXt(\n");
125 			foreach(const(PngTextKeywords) k, string v; *tEXt) {
126 				addText("\t\t\t[", k, "]: {", v, "}\n");
127 			}
128 			addText("\t\t)\n");
129 		}
130         if (zEXt.keys.length > 0) {
131 			addText("\t\tzEXt(\n");
132 			foreach(const(PngTextKeywords) k, string v; *zEXt) {
133 				addText("\t\t\t[", k, "]: {", v, "}\n");
134 			}
135 			addText("\t\t)\n");
136 		}
137         
138         if (PLTE !is null)
139             addText("\t\t", (*PLTE).text, ",\n");
140         if (tRNS !is null)
141             addText("\t\t", (*tRNS).text, ",\n");
142         if (gAMA !is null)
143             addText("\t\t", (*gAMA).text, ",\n");
144         if (cHRM !is null)
145             addText("\t\t", (*cHRM).text, ",\n");
146         if (sRGB !is null)
147             addText("\t\t", (*sRGB).text, ",\n");
148         if (iCCP !is null)
149             addText("\t\t", (*iCCP).text, ",\n");
150         if (bKGD !is null)
151             addText("\t\t", (*bKGD).text, ",\n");
152         if (pPHs !is null)
153             addText("\t\t", (*pPHs).text, ",\n");
154         if (sBIT !is null)
155             addText("\t\t", (*sBIT).text, ",\n");
156         if (tIME !is null)
157             addText("\t\t", (*tIME).text, ",\n");
158         
159         if (sPLT.length > 0)
160             addText("\t\t", (*sPLT)[].text, ",\n");
161         if (hIST.length > 0)
162             addText("\t\t", (*hIST)[].text, ",\n");
163         
164         addText("\t]\n");
165         static if (!is(Color == HeadersOnly)) {
166             addText("\tData: [\n");
167             
168             foreach(y; 0 .. value.height) {
169                 addText("\t\t(y: ", y.text, " ");
170                 foreach(x; 0 .. value.width) {
171                     addText("(x: ", x.text, " ", value.getPixel(x, y).text, ")"); // FIXME: text allocates
172                     if (x + 1 < value.width)
173                         addText(", ");
174                 }
175                 addText(")");
176                 if (y + 1 < value.height)
177                     addText(",\n");
178                 else
179                     addText("\n");
180             }
181             
182             addText("\t]\n");
183         }
184         
185         addText("]");
186         return cast(string)ret;
187     }
188     
189     @property {
190         ///
191         IAllocator allocator() {
192             return alloc;
193         }
194     }
195     
196 	this(IAllocator allocator) {
197 		this.alloc = allocator;
198 		tEXt = allocator.make!(HashMap!(PngTextKeywords, string, IAllocator))(allocator);
199 		zEXt = allocator.make!(HashMap!(PngTextKeywords, string, IAllocator))(allocator);
200 		sPLT = allocator.make!(DynamicArray!(sPLT_Chunk, IAllocator))(allocator);
201 		hIST = allocator.make!(DynamicArray!(ushort, IAllocator))(allocator);
202 	}
203 
204 	~this() {
205 		if (allocator is null) return;
206 		
207 		if (PLTE !is null)
208 			allocator.dispose(PLTE);
209 		if (tRNS !is null)
210 			allocator.dispose(tRNS);
211 		if (gAMA !is null)
212 			allocator.dispose(gAMA);
213 		if (cHRM !is null)
214 			allocator.dispose(cHRM);
215 		if (sRGB !is null)
216 			allocator.dispose(sRGB);
217 		if (iCCP !is null)
218 			allocator.dispose(iCCP);
219 		if (bKGD !is null)
220 			allocator.dispose(bKGD);
221 		if (pPHs !is null)
222 			allocator.dispose(pPHs);
223 		if (sBIT !is null)
224 			allocator.dispose(sBIT);
225 		if (tIME !is null)
226 			allocator.dispose(tIME);
227 
228 		static if (!is(Color == HeadersOnly)) {
229 			if (IDAT !is null) {
230 				allocator.dispose(IDAT.data);
231 				allocator.dispose(IDAT);
232 			}
233 			if (value !is null) {
234 				allocator.dispose(value);
235 			}
236 		}
237 	}
238 
239     private {
240         IAllocator alloc;
241         
242         void delegate(size_t width, size_t height) @trusted theImageAllocator;
243         
244         static if (!is(Color == HeadersOnly)) {
245             IDAT_Chunk!Color* IDAT;
246             
247             void allocateTheImage(ImageImpl)(size_t width, size_t height) @trusted {
248                 static if (is(ImageImpl : ImageStorage!Color)) {
249                     value = alloc.make!(ImageImpl)(width, height, alloc);
250                 } else {
251                     value = imageObject!(ImageImpl)(width, height, alloc);
252                 }
253             }
254         } else {
255             // this gets checked for so many places, that it would be a pain to actually fix it there.
256             // instead declare it as an untyped pointer to emulate 'is null'.
257             // it should _never_ be assigned to!
258             void* IDAT = null;
259         }
260         
261         /*
262          * The importer
263          */
264         
265         void performInput(IR)(IR input) @trusted {
266             import std.range;
267             ubyte[] buffer = allocator.makeArray!ubyte(1024 * 1024 * 8); // 8mb
268             
269             ubyte popReadValue() {
270                 if (input.empty)
271                     throw new ImageNotLoadableException("Input was not long enough");
272 
273                 ubyte ret = input.front;
274                 input.popFront;
275 
276                 return ret;
277             }
278             
279             bool checkHasPNGText() {
280                 if (!popReadValue == 0x89)
281                     return false;
282                 if (!popReadValue == 0x50)
283                     return false;
284                 if (!popReadValue == 0x4E)
285                     return false;
286                 if (!popReadValue == 0x47)
287                     return false;
288                 if (!popReadValue == 0x0D)
289                     return false;
290                 if (!popReadValue == 0x0A)
291                     return false;
292                 if (!popReadValue == 0x1A)
293                     return false;
294                 if (!popReadValue == 0x0A)
295                     return false;
296                 return true;
297             }
298             if (!checkHasPNGText()) {
299                 throw new ImageNotLoadableException("Input was not a PNG image");
300             }
301             
302             ubyte[] readChunk(out char[4] name) {
303                 import std.digest.crc : crc32Of, crcHexString;
304                 import std.conv : to;
305                 ubyte[4] tuintbuff;
306                 
307                 // chunk length
308                 tuintbuff[0] = popReadValue;
309                 tuintbuff[1] = popReadValue;
310                 tuintbuff[2] = popReadValue;
311                 tuintbuff[3] = popReadValue;
312                 uint chunkLength = bigEndianToNative!uint(tuintbuff);
313                 
314                 // chunk name
315                 name[0] = popReadValue;
316                 name[1] = popReadValue;
317                 name[2] = popReadValue;
318                 name[3] = popReadValue;
319                 
320                 // chunk data
321                 if (buffer.length < chunkLength + 4)
322                     allocator.expandArray(buffer, chunkLength + 4);
323                 
324                 buffer[0 .. 4] = cast(ubyte[4])name[];
325                 foreach(index; 4 .. chunkLength + 4) {
326                     buffer[index] = popReadValue;
327                 }
328                 
329                 // get the CRC code for chunk
330                 tuintbuff[0] = popReadValue;
331                 tuintbuff[1] = popReadValue;
332                 tuintbuff[2] = popReadValue;
333                 tuintbuff[3] = popReadValue;
334                 uint crcCode = bigEndianToNative!uint(tuintbuff);
335                 
336                 // check the chunk has not been "tampered" with
337                 // FIXME: probably allocated in crcHexString ew
338                 if (crcHexString(crc32Of(buffer[0 .. chunkLength + 4])) != crcHexString((cast(ubyte*)&crcCode)[0 .. 4])) {
339                     throw allocator.make!ImageNotLoadableException("CRC code did not match for chunk");
340                 }
341                 
342                 return buffer[4 .. chunkLength + 4];
343             }
344             
345         WL: while(!input.empty) {
346                 char[4] chunkName;
347                 ubyte[] chunkData = readChunk(chunkName);
348                 
349                 switch(chunkName) {
350                     case "IHDR":
351                         readChunk_IHDR(chunkData);
352                         break;
353                     case "cHRM":
354                         // preceede IDAT, PLTE _only_
355                         if (IDAT !is null || PLTE !is null)
356                             throw allocator.make!ImageNotLoadableException("cHRM chunk must preceede IDAT or PLTE chunks");
357                         else if (cHRM !is null) // must not have a iCCP chunk as well
358                             throw allocator.make!ImageNotLoadableException("Only one cHRM chunk can exist");
359                         
360                         readChunk_cHRM(chunkData);
361                         break;
362                     case "sRGB":
363                         // preceede IDAT, PLTE _only_
364                         if (IDAT !is null || PLTE !is null)
365                             throw allocator.make!ImageNotLoadableException("sRGB chunk must preceede IDAT or PLTE chunks");
366                         else if (iCCP !is null) // must not have a iCCP chunk as well
367                             throw allocator.make!ImageNotLoadableException("sRGB chunk must not exist along with iCCP chunk");
368                         else if (sRGB !is null) // must not have a sRGB chunk as well
369                             throw allocator.make!ImageNotLoadableException("Only one sRGB chunk can exist");
370                         
371                         readChunk_sRGB(chunkData);
372                         break;
373                     case "iCCP":
374                         // preceede IDAT, PLTE _only_
375                         if (IDAT !is null || PLTE !is null)
376                             throw allocator.make!ImageNotLoadableException("iCCP chunk must preceede IDAT or PLTE chunks");
377                         else if (sRGB !is null) // must not have a sRGB chunk as well
378                             throw allocator.make!ImageNotLoadableException("iCCP chunk must not exist along with sRGB chunk");
379                         else if (iCCP !is null) // must not have a iCCP chunk as well
380                             throw allocator.make!ImageNotLoadableException("Only one iCCP chunk can exist");
381                         
382                         readChunk_iCCP(chunkData);
383                         break;
384                     case "PLTE":
385                         if (IDAT !is null || bKGD !is null)
386                             throw allocator.make!ImageNotLoadableException("iCCP chunk must preceede IDAT and bKGD chunk(s)");
387                         else if (PLTE !is null) // must not have a PLTE chunk as well
388                             throw allocator.make!ImageNotLoadableException("Only one PLTE chunk can exist");
389                         
390                         readChunk_PLTE(chunkData);
391                         break;
392                     case "gAMA":
393                         if (IDAT !is null || PLTE !is null)
394                             throw allocator.make!ImageNotLoadableException("gAMA chunk must preceede IDAT or PLTE chunks");
395                         else if (gAMA !is null) // must not have a gAMA chunk as well
396                             throw allocator.make!ImageNotLoadableException("Only one gAMA chunk can exist");
397                         
398                         readChunk_gAMA(chunkData);
399                         break;
400                     case "tRNS":
401                         if (IDAT !is null || PLTE !is null)
402                             throw allocator.make!ImageNotLoadableException("tRNS chunk must preceede IDAT or PLTE chunks");
403                         else if (tRNS !is null) // must not have a tRNS chunk as well
404                             throw allocator.make!ImageNotLoadableException("Only one tRNS chunk can exist");
405                         
406                         readChunk_tRNS(chunkData);
407                         break;
408                     case "tEXt":
409                         readChunk_tEXt(chunkData);
410                         break;
411                     case "zEXt":
412                         readChunk_zEXt(chunkData);
413                         break;
414                     case "iTXt":
415                         readChunk_iEXt(chunkData);
416                         break;
417                     case "bKGD":
418                         if (IDAT !is null)
419                             throw allocator.make!ImageNotLoadableException("bKGD chunk must preceede IDAT chunks");
420                         else if (bKGD !is null) // must not have a tRNS chunk as well
421                             throw allocator.make!ImageNotLoadableException("Only one bKGD chunk can exist");
422                         
423                         readChunk_bKGD(chunkData);
424                         break;
425                     case "pPHs":
426                         if (IDAT !is null)
427                             throw allocator.make!ImageNotLoadableException("pPHs chunk must preceede IDAT chunks");
428                         else if (pPHs !is null) // must not have a tRNS chunk as well
429                             throw allocator.make!ImageNotLoadableException("Only one pPHs chunk can exist");
430                         
431                         readChunk_pPHs(chunkData);
432                         break;
433                     case "sBIT":
434                         if (IDAT !is null)
435                             throw allocator.make!ImageNotLoadableException("sBIT chunk must preceede IDAT chunks");
436                         else if (sBIT !is null) // must not have a tRNS chunk as well
437                             throw allocator.make!ImageNotLoadableException("Only one sBIT chunk can exist");
438                         
439                         readChunk_sBIT(chunkData);
440                         break;
441                     case "sPLT":
442                         if (IDAT !is null)
443                             throw allocator.make!ImageNotLoadableException("sPLT chunk must preceede IDAT chunks");
444                         
445                         readChunk_sPLT(chunkData);
446                         break;
447                     case "hIST":
448                         if (IDAT !is null)
449                             throw allocator.make!ImageNotLoadableException("hIST chunk must preceede IDAT chunks");
450                         else if (PLTE !is null)
451                             throw allocator.make!ImageNotLoadableException("hIST chunk must proceed PLTE chunk");
452                         
453                         readChunk_hIST(chunkData);
454                         break;
455                         
456                     case "tIME":
457                         readChunk_tIME(chunkData);
458                         break;
459                         
460                         static if (!is(Color == HeadersOnly)) {
461                             case "IDAT":
462                             if (hIST.length > 0 && PLTE.colors.length != hIST.length)
463                                 throw allocator.make!ImageNotLoadableException("hIST and PLTE chunks must have the same index length");
464                             if ((IHDR.colorType & PngIHDRColorType.Palette) == PngIHDRColorType.Palette && tRNS !is null && tRNS.indexAlphas.length < PLTE.colors.length)
465                                 allocator.expandArray(tRNS.indexAlphas, tRNS.indexAlphas.length - PLTE.colors.length, 255);
466                             
467                             if (IDAT is null) {// allocate the image storage
468                                 IDAT = allocator.make!(IDAT_Chunk!Color);
469 								IDAT.data = allocator.makeArray!ubyte(chunkData.length);
470                                 theImageAllocator(IHDR.width, IHDR.height);
471 							} else
472 								allocator.expandArray(IDAT.data, chunkData.length);
473                             IDAT.data[$-chunkData.length .. $] = chunkData[];
474                             break;
475                         }
476                         
477                     case "IEND":
478                         static if (!is(Color == HeadersOnly)) {
479                             if (IDAT is null)
480                                 throw allocator.make!ImageNotLoadableException("No IDAT chunk present");
481 
482                             readChunk_IDAT(IDAT.data);
483                         }
484                         
485                         readChunk_IEND(chunkData);
486                         break WL;
487                     default:
488                         break;
489                 }
490             }
491             
492             allocator.dispose(buffer);
493         }
494         
495         void readChunk_IHDR(ubyte[] chunkData) @trusted {
496             if (chunkData.length != 13)
497                 throw allocator.make!ImageNotLoadableException("IHDR chunk size must be 13 bytes long");
498             
499             IHDR = IHDR_Chunk(
500                 bigEndianToNative!uint(cast(ubyte[4])chunkData[0 .. 4]),
501                 bigEndianToNative!uint(cast(ubyte[4])chunkData[4 .. 8]),
502                 cast(PngIHDRBitDepth) chunkData[8],
503                 cast(PngIHDRColorType) chunkData[9],
504                 cast(PngIHDRCompresion) chunkData[10],
505                 cast(PngIHDRFilter) chunkData[11],
506                 cast(PngIHDRInterlaceMethod) chunkData[12]);
507         }
508         
509         void readChunk_cHRM(ubyte[] chunkData) @trusted {
510             if (chunkData.length != 32)
511                 throw allocator.make!ImageNotLoadableException("cHRM chunk must be 32 bytes long");
512             
513             cHRM = allocator.make!cHRM_Chunk(
514                 bigEndianToNative!uint(cast(ubyte[4])chunkData[0 .. 4]),
515                 bigEndianToNative!uint(cast(ubyte[4])chunkData[4 .. 8]),
516                 bigEndianToNative!uint(cast(ubyte[4])chunkData[8 .. 12]),
517                 bigEndianToNative!uint(cast(ubyte[4])chunkData[12 .. 16]),
518                 bigEndianToNative!uint(cast(ubyte[4])chunkData[16 .. 20]),
519                 bigEndianToNative!uint(cast(ubyte[4])chunkData[20 .. 24]),
520                 bigEndianToNative!uint(cast(ubyte[4])chunkData[24 .. 28]),
521                 bigEndianToNative!uint(cast(ubyte[4])chunkData[28 .. 32]));
522         }
523         
524         void readChunk_sRGB(ubyte[] chunkData) @trusted {
525             if (chunkData.length != 1)
526                 throw allocator.make!ImageNotLoadableException("cHRM chunk must be 1 byte long");
527             
528             sRGB = allocator.make!sRGB_Chunk(cast(PngRenderingIntent)chunkData[0]);
529         }
530         
531         void readChunk_iCCP(ubyte[] chunkData) @trusted {
532             import std.zlib : uncompress;
533             
534             char[] profileName;
535             foreach(i, c; chunkData) {
536                 if (i >= 80)
537                     throw allocator.make!ImageNotLoadableException("iCCP chunk profile name must be less then 80 characters long (c-string\\0)");
538                 
539                 if (c == 0)  {// null terminator
540                     if (i == 0) // error
541                         throw allocator.make!ImageNotLoadableException("iCCP chunk profile name must be atleast 1 character in length");
542                     
543                     profileName = cast(char[])chunkData[0 .. i];
544                     
545                     if (i + 2 < chunkData.length)
546                         throw allocator.make!ImageNotLoadableException("iCCP chunk data is not long enough");
547                     chunkData = chunkData[i + 1 .. $];
548                     break;
549                 }
550             }
551             
552             iCCP = allocator.make!iCCP_Chunk();
553             
554             // profile name
555             iCCP.profileName = cast(string)allocator.makeArray!char(profileName.length);
556             cast(char[])iCCP.profileName[] = profileName[];
557             
558             // compression method deflate/inflate
559             iCCP.compressionMethod = cast(PngIHDRCompresion)chunkData[0];
560             
561             //
562             if (iCCP.compressionMethod == PngIHDRCompresion.DeflateInflate) {
563                 iCCP.profile = cast(ubyte[])uncompress(chunkData[1 .. $]);
564             } else {
565                 throw allocator.make!ImageNotLoadableException("Unknown iCCP chunk compression method");
566             }
567         }
568         
569         void readChunk_PLTE(ubyte[] chunkData) @trusted {
570             PLTE = allocator.make!PLTE_Chunk;
571             
572             if ((chunkData.length % 3) > 0)
573                 throw allocator.make!ImageNotLoadableException("PLTE chunk size must be devisible by 3");
574             else if ((chunkData.length / 3) > 256)
575                 throw allocator.make!ImageNotLoadableException("PLTE chunk must contain at the most 256 entries");
576             
577             PLTE.colors = allocator.makeArray!(PLTE_Chunk.Color)(chunkData.length / 3);
578 
579             size_t offset;
580             for (size_t i; i < (chunkData.length / 3); i++) {
581                 PLTE.colors[i] = PLTE_Chunk.Color(chunkData[offset], chunkData[offset + 1], chunkData[offset + 2]);
582                 offset += 3;
583             }
584         }
585         
586         void readChunk_gAMA(ubyte[] chunkData) @trusted {
587             if (chunkData.length != 4)
588                 throw allocator.make!ImageNotLoadableException("gAMA chunk must be 4 bytes in size");
589             
590             gAMA = allocator.make!gAMA_Chunk(bigEndianToNative!uint(cast(ubyte[4])chunkData[0 .. 4]));    
591         }
592         
593         void readChunk_tRNS(ubyte[] chunkData) @trusted {
594             import std.range : inputRangeObject;
595             tRNS = allocator.make!tRNS_Chunk();
596             
597             if (IHDR.colorType & PngIHDRColorType.Palette) {
598                 tRNS.indexAlphas = allocator.makeArray!ubyte(chunkData.length);
599                 tRNS.indexAlphas[] = chunkData[];
600             } else if (IHDR.colorType & PngIHDRColorType.Grayscale) {
601                 if (chunkData.length != 2)
602                     throw allocator.make!ImageNotLoadableException("tRNS chunk size must be 2 bytes when it is grayscale");
603                 
604                 if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
605                     ushort c = bigEndianToNative!ushort(cast(ubyte[2])chunkData[0 .. 2]);
606                     tRNS.b16 = RGB16(c, c, c);
607                 } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
608                     // TODO: confirm that it is the first byte and not the second?
609                     tRNS.b8 = RGB8(chunkData[0], chunkData[0], chunkData[0]);
610                 } else {
611                     ubyte c = cast(ubyte)(chunkData[0] * cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1));
612                     tRNS.b8 = RGB8(c, c, c);
613                 }
614             } else if (IHDR.colorType & PngIHDRColorType.ColorUsed) {
615                 if (chunkData.length != 3)
616                     throw allocator.make!ImageNotLoadableException("tRNS chunk size must be 2 bytes when it is grayscale");
617                 
618                 if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
619                     ushort r = bigEndianToNative!ushort(cast(ubyte[2])chunkData[0 .. 2]);
620                     ushort g = bigEndianToNative!ushort(cast(ubyte[2])chunkData[2 .. 4]);
621                     ushort b = bigEndianToNative!ushort(cast(ubyte[2])chunkData[4 .. 6]);
622                     tRNS.b16 = RGB16(r, g, b);
623                 } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
624                     // TODO: confirm that it is the first byte and not the second?
625                     tRNS.b8 = RGB8(chunkData[0], chunkData[2], chunkData[4]);
626                 } else {
627                     tRNS.b8 = RGB8(cast(ubyte)(chunkData[0] * cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1)),
628                         cast(ubyte)(chunkData[2] * cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1)),
629                         cast(ubyte)(chunkData[4] * cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1)));
630                 }
631             }
632         }
633         
634         void readChunk_tEXt(ubyte[] chunkData) @trusted {
635             size_t sinceLast;
636             ubyte[] buffer;
637             ubyte[] keyword;
638             
639             if (chunkData.length < 2)
640                 throw allocator.make!ImageNotLoadableException("tEXt chunk must be atleast 2 bytes");
641             
642             foreach(i, c; chunkData) {
643                 if (c == 0) {
644                     keyword = buffer;
645                     buffer = null;
646                     sinceLast = i + 1;
647                 } else {
648                     buffer = chunkData[sinceLast .. i + 1];
649                 }
650             }
651             
652             char[] keyword2 = allocator.makeArray!char(keyword.length);
653             keyword2[] = cast(char[])keyword[];
654             char[] buffer2 = allocator.makeArray!char(buffer.length);
655             buffer2[] = cast(char[])buffer[];
656             
657             (*tEXt)[cast(PngTextKeywords)keyword2] = cast(string)buffer2;
658         }
659         
660         void readChunk_zEXt(ubyte[] chunkData) @trusted {
661             import std.zlib : uncompress;
662             
663             size_t sinceLast;
664             ubyte[] buffer;
665             ubyte[] keyword;
666             
667             if (chunkData.length < 2)
668                 throw allocator.make!ImageNotLoadableException("zEXt chunk must be atleast 2 bytes");
669             
670             foreach(i, c; chunkData) {
671                 if (c == 0) {
672                     keyword = buffer;
673                     buffer = null;
674                     sinceLast = i + 1;
675                 } else {
676                     buffer = chunkData[sinceLast .. i + 1];
677                 }
678             }
679             
680             char[] keyword2 = allocator.makeArray!char(keyword.length);
681             keyword2[] = cast(char[])keyword[];
682             
683             if (buffer.length > 0)
684                 throw allocator.make!ImageNotLoadableException("zEXt chunk must have a compression method");
685             
686             ubyte compressionMethod = buffer[0];
687             if (compressionMethod > 0)
688                 throw allocator.make!ImageNotLoadableException("zEXt chunk unknown compression method");
689             
690             buffer = cast(ubyte[])uncompress(buffer);
691             
692             char[] buffer2 = allocator.makeArray!char(buffer.length - 1);
693             buffer2[] = cast(char[])buffer[1 .. $];
694             
695             (*zEXt)[cast(PngTextKeywords)keyword2] = cast(string)buffer2;
696         }
697         
698         void readChunk_iEXt(ubyte[] chunkData) @trusted {
699             import std.zlib : uncompress;
700             
701             size_t sinceLast;
702             ubyte[] buffer;
703             ubyte[] keyword;
704             
705             if (chunkData.length < 2)
706                 throw allocator.make!ImageNotLoadableException("iEXt chunk must be atleast 2 bytes");
707             
708             foreach(i, c; chunkData) {
709                 if (c == 0 && keyword !is null) {
710                     //keyword = buffer;
711                     buffer = null;
712                     sinceLast = i + 1;
713                 } else {
714                     buffer = chunkData[sinceLast .. i + 1];
715                 }
716             }
717             
718             if (buffer.length > 0)
719                 throw allocator.make!ImageNotLoadableException("iEXt chunk must have a compression method");
720             bool useCompression = cast(bool)buffer[0];
721             ubyte compressionMethod = buffer[1];
722             if (useCompression && compressionMethod > 0)
723                 throw allocator.make!ImageNotLoadableException("iEXt chunk unknown compression method");
724             buffer = buffer[1 .. $];
725             
726             if (buffer.length > 0)
727                 throw allocator.make!ImageNotLoadableException("iEXt chunk must have a language");
728             
729             ubyte[] buffer2;
730             ubyte[] language;
731             foreach(i, c; buffer) {
732                 if (c == 0 && language !is null) {
733                     language = buffer2;
734                     buffer2 = null;
735                     sinceLast = i + 1;
736                 } else {
737                     buffer2 = buffer[sinceLast .. i + 1];
738                 }
739             }
740             buffer = buffer2;
741             
742             if (buffer.length > 0)
743                 throw allocator.make!ImageNotLoadableException("iEXt chunk must have a translated keyword");
744             
745             foreach(i, c; buffer) {
746                 if (c == 0 && language !is null) {
747                     keyword = buffer2;
748                     buffer2 = null;
749                     sinceLast = i + 1;
750                 } else {
751                     buffer2 = buffer[sinceLast .. i + 1];
752                 }
753             }
754             buffer = buffer2;
755             
756             if (useCompression && compressionMethod == 0)
757                 buffer = cast(ubyte[])uncompress(buffer);
758             
759             char[] keyword2 = allocator.makeArray!char(keyword.length);
760             keyword2[] = cast(char[])keyword[];
761             
762             char[] buffer3 = allocator.makeArray!char(buffer.length);
763             buffer3 = cast(char[])buffer[];
764             
765             if (useCompression)
766                 (*zEXt)[cast(PngTextKeywords)keyword2] = cast(string)buffer3;
767             else
768                 (*tEXt)[cast(PngTextKeywords)keyword2] = cast(string)buffer3;
769         }
770         
771         void readChunk_bKGD(ubyte[] chunkData) @trusted {
772             bKGD = allocator.make!bKGD_Chunk();
773             
774             if (IHDR.colorType & PngIHDRColorType.Palette) {
775                 if (chunkData.length != 2)
776                     throw allocator.make!ImageNotLoadableException("bKGD chunk size must be 1 bytes when it is palette");
777                 
778                 bKGD.index = chunkData[0];
779             } else if (IHDR.colorType & PngIHDRColorType.Grayscale) {
780                 if (chunkData.length != 2)
781                     throw allocator.make!ImageNotLoadableException("bKGD chunk size must be 2 bytes when it is grayscale");
782                 
783                 if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
784                     ushort c = bigEndianToNative!ushort(cast(ubyte[2])chunkData[0 .. 2]);
785                     bKGD.b16 = RGB16(c, c, c);
786                 } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
787                     // TODO: confirm that it is the first byte and not the second?
788                     bKGD.b8 = RGB8(chunkData[0], chunkData[0], chunkData[0]);
789                 } else {
790                     ubyte c = cast(ubyte)(chunkData[0] * cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1));
791                     bKGD.b8 = RGB8(c, c, c);
792                 }
793             } else if (IHDR.colorType & PngIHDRColorType.ColorUsed) {
794                 if (chunkData.length != 3)
795                     throw allocator.make!ImageNotLoadableException("bKGD chunk size must be 2 bytes when it is grayscale");
796                 
797                 if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
798                     ushort r = bigEndianToNative!ushort(cast(ubyte[2])chunkData[0 .. 2]);
799                     ushort g = bigEndianToNative!ushort(cast(ubyte[2])chunkData[2 .. 4]);
800                     ushort b = bigEndianToNative!ushort(cast(ubyte[2])chunkData[4 .. 6]);
801                     bKGD.b16 = RGB16(r, g, b);
802                 } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
803                     // TODO: confirm that it is the first byte and not the second?
804                     bKGD.b8 = RGB8(chunkData[0], chunkData[2], chunkData[4]);
805                 } else {
806                     bKGD.b8 = RGB8(cast(ubyte)(chunkData[0] * cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1)),
807                         cast(ubyte)(chunkData[2] * cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1)),
808                         cast(ubyte)(chunkData[4] * cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1)));
809                 }
810             }
811         }
812         
813         void readChunk_pPHs(ubyte[] chunkData) @trusted {
814             pPHs = allocator.make!pPHs_Chunk();
815             if (chunkData.length != 9)
816                 throw allocator.make!ImageNotLoadableException("pPHs chunk size must be 9 bytes");
817             
818             pPHs.ppx = bigEndianToNative!uint(cast(ubyte[4])chunkData[0 .. 4]);
819             pPHs.ppy = bigEndianToNative!uint(cast(ubyte[4])chunkData[4 .. 8]);
820             pPHs.unit = cast(PngPhysicalPixelUnit)chunkData[8];
821         }
822         
823         void readChunk_sBIT(ubyte[] chunkData) @trusted {
824             sBIT = allocator.make!sBIT_Chunk();
825             
826             if (IHDR.colorType & PngIHDRColorType.Palette) {
827                 if (chunkData.length != 3)
828                     throw allocator.make!ImageNotLoadableException("sBIT chunk size must be 3 byte for palette color type");
829                 
830                 sBIT.indexed[] = chunkData[0 .. 3];
831             } else if (IHDR.colorType & PngIHDRColorType.Grayscale) {
832                 bool withAlpha = (IHDR.colorType & PngIHDRColorType.AlphaChannelUsed) == PngIHDRColorType.AlphaChannelUsed;
833                 
834                 if ((!withAlpha && chunkData.length != 1) || (withAlpha && chunkData.length != 2))
835                     throw allocator.make!ImageNotLoadableException("sBIT chunk size must be 1 byte for grayscale color type and 2 for grayscale with alpha");
836                 
837                 if (withAlpha)
838                     sBIT.grayScaleAlpha[] = chunkData[0 .. 2];
839                 else
840                     sBIT.grayScale = chunkData[0];
841             } else if (IHDR.colorType & PngIHDRColorType.ColorUsed) {
842                 bool withAlpha = (IHDR.colorType & PngIHDRColorType.AlphaChannelUsed) == PngIHDRColorType.AlphaChannelUsed;
843                 
844                 if ((!withAlpha && chunkData.length != 3) || (withAlpha && chunkData.length != 4))
845                     throw allocator.make!ImageNotLoadableException("sBIT chunk size must be 3 bytes for truecolor color type and 4 for truecolor with alpha");
846                 
847                 if (withAlpha)
848                     sBIT.trueColorAlpha[] = chunkData[0 .. 4];
849                 else
850                     sBIT.trueColor[] = chunkData[0 .. 3];
851             }
852         }
853         
854         void readChunk_sPLT(ubyte[] chunkData) @trusted {
855             sPLT_Chunk chunk;
856             
857             if (chunkData.length < 2)
858                 throw allocator.make!ImageNotLoadableException("sPLT chunk size must greater than 2 bytes");
859             
860             ubyte[] buffer;
861             char[] name;
862             size_t sinceLast;
863             
864             foreach(i, c; chunkData) {
865                 if (c == 0 && name !is null) {
866                     name = cast(char[])buffer;
867                     buffer = null;
868                     sinceLast = i + 1;
869                 } else {
870                     buffer = buffer[sinceLast .. i + 1];
871                 }
872             }
873             
874             chunk.paletteName = cast(string)allocator.makeArray!char(buffer.length);
875             (cast(char[])chunk.paletteName)[] = name;
876             
877             if (buffer.length < 2)
878                 throw allocator.make!ImageNotLoadableException("sPLT chunk size must be greater than 1 byte for sample depth");
879             
880             chunk.sampleDepth = cast(PngIHDRBitDepth)buffer[0];
881             buffer = buffer[1 .. $];
882             
883             size_t count;
884             if (chunk.sampleDepth == PngIHDRBitDepth.BitDepth8) {
885                 if (buffer.length % 6 == 0) {
886                     chunk.colors = allocator.makeArray!(sPLT_Chunk.Entry)(buffer.length / 6);
887                     
888                     for(size_t i; i < buffer.length; i += 6) {
889                         ubyte[2] values;
890                         values[] = chunkData[i + 4 .. i + 6];
891                         
892                         chunk.colors[count].color.b8 = RGBA8(chunkData[i], chunkData[i + 1], chunkData[i + 2], chunkData[i + 3]);
893                         chunk.colors[count].frequency = bigEndianToNative!ushort(values);
894                         count++;
895                     }
896                 } else {
897                     throw allocator.make!ImageNotLoadableException("sPLT chunk palette must be devisible by 6 for sample depth of 8");
898                 }
899             } else if (chunk.sampleDepth == PngIHDRBitDepth.BitDepth16) {
900                 if (buffer.length % 10 == 0) {
901                     chunk.colors = allocator.makeArray!(sPLT_Chunk.Entry)(buffer.length / 10);
902                     
903                     for(size_t i; i < buffer.length; i += 10) {
904                         ubyte[2][5] values;
905                         values[0][] = chunkData[i .. i + 2];
906                         values[1][] = chunkData[i + 2 .. i + 4];
907                         values[2][] = chunkData[i + 4 .. i + 6];
908                         values[3][] = chunkData[i + 6 .. i + 8];
909                         values[4][] = chunkData[i + 8 .. i + 10];
910                         
911                         chunk.colors[count].color.b16 = RGBA16(
912                             bigEndianToNative!ushort(values[0]),
913                             bigEndianToNative!ushort(values[1]),
914                             bigEndianToNative!ushort(values[2]),
915                             bigEndianToNative!ushort(values[3]));
916                         chunk.colors[count].frequency = bigEndianToNative!ushort(values[4]);
917                         count++;
918                     }
919                 } else {
920                     throw allocator.make!ImageNotLoadableException("sPLT chunk palette must be devisible by 10 for sample depth of 16");
921                 }
922             } else {
923                 throw allocator.make!ImageNotLoadableException("sPLT chunk must have a bit depth of either 8 or 16");
924             }
925             
926             *sPLT ~= chunk;
927         }
928         
929         void readChunk_hIST(ubyte[] chunkData) @trusted {
930             if (chunkData.length % 2 == 1)
931                 throw allocator.make!ImageNotLoadableException("hIST chunk must be devisible by 2");
932             size_t count = chunkData.length / 2;
933             
934             size_t ci;
935             foreach(i; hIST.length - count .. hIST.length) {
936                 ubyte[2] values;
937                 values[] = chunkData[ci .. ci + 2];
938                 *hIST ~= bigEndianToNative!ushort(values);
939                 
940                 ci += 2;
941             }
942         }
943         
944         void readChunk_tIME(ubyte[] chunkData) @trusted {
945             if (chunkData.length != 7)
946                 throw allocator.make!ImageNotLoadableException("tIME chunk must be 7 bytes");
947             
948             int[6] values = [
949                 bigEndianToNative!short(cast(ubyte[2])chunkData[0 .. 2]),
950                 chunkData[2], chunkData[3], chunkData[4], chunkData[5], chunkData[6]
951             ];
952             
953             if ((values[1] >= 1 && values[1] <= 12) &&
954                 (values[2] >= 1 && values[2] <= 31) &&
955                 (values[3] >= 0 && values[3] <= 23) &&
956                 (values[4] >= 0 && values[4] <= 59) &&
957                 (values[5] >= 0 && values[5] <= 60)) {}
958             else
959                 throw allocator.make!ImageNotLoadableException("tIME chunk date time value is invalid");
960             
961             tIME = allocator.make!DateTime(values[0], values[1], values[2], values[3], values[4], values[5]);
962         }
963         
964         static if (!is(Color == HeadersOnly)) {
965             void readChunk_IDAT(ubyte[] chunkData) @trusted {
966                 import std.zlib : uncompress; // FIXME: std.zlib allocates without using the allocator *grumbles*
967                 import std.math : ceil, floor;
968 
969                 // a simple check
970                 if (IHDR.interlaceMethod == PngIHDRInterlaceMethod.Adam7 || IHDR.interlaceMethod == PngIHDRInterlaceMethod.NoInterlace)
971                 {} else
972                     throw allocator.make!ImageNotLoadableException("IDAT unknown interlace method");
973 
974                 if (IHDR.compressionMethod == PngIHDRCompresion.DeflateInflate) {}
975                 else
976                     throw allocator.make!ImageNotLoadableException("IDAT unknown compression method");
977 
978                 // constants
979                 size_t pixelPreviousByteAmount;
980                 size_t totalSize, scanLineSize;
981                 size_t[7] /+rowsPerPass, +/scanLinesSize;
982 
983                 bool withAlpha = (IHDR.colorType & PngIHDRColorType.AlphaChannelUsed) == PngIHDRColorType.AlphaChannelUsed;
984                 bool isGrayScale = (IHDR.colorType & PngIHDRColorType.Grayscale) == PngIHDRColorType.Grayscale;
985                 bool isPalette = (IHDR.colorType & PngIHDRColorType.Palette) == PngIHDRColorType.Palette;
986                 bool isColor = (IHDR.colorType & PngIHDRColorType.ColorUsed) == PngIHDRColorType.ColorUsed;
987 
988                 // some needed variables, in future processing
989                 ubyte[] decompressed, previousScanLine, tempBitDepth124, myAdaptiveOffsets;
990                 ubyte pass, sampleSize, pixelSampleSize;
991                 size_t offsetX, offsetY, offset, currentRow;
992 
993                 final switch(IHDR.colorType) {
994                     case PngIHDRColorType.AlphaChannelUsed:
995                         sampleSize = 2;
996                         break;
997                     case PngIHDRColorType.PalletteWithColorUsed:
998                     case PngIHDRColorType.Palette:
999                     case PngIHDRColorType.Grayscale:
1000                         sampleSize = 1;
1001                         break;
1002                     case PngIHDRColorType.ColorUsedWithAlpha:
1003                         sampleSize = 4;
1004                         break;
1005                     case PngIHDRColorType.ColorUsed:
1006                         sampleSize = 3;
1007                         break;
1008                 }
1009 
1010                 if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
1011                     pixelSampleSize = cast(ubyte)(sampleSize + sampleSize);
1012                     pixelPreviousByteAmount = pixelSampleSize;
1013                     scanLineSize = pixelSampleSize * IHDR.width;
1014                 } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
1015                     pixelSampleSize = sampleSize;
1016                     pixelPreviousByteAmount = sampleSize;
1017                     scanLineSize = sampleSize * IHDR.width;
1018                 } else {
1019                     pixelSampleSize = sampleSize;
1020                     pixelPreviousByteAmount = 1;
1021                     tempBitDepth124 = alloc.makeArray!ubyte(8);
1022                     
1023                     if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth1)
1024                         scanLineSize = cast(size_t)ceil((sampleSize * IHDR.width) / 8f);
1025                     else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth2)
1026                         scanLineSize = cast(size_t)ceil((sampleSize * IHDR.width) / 4f);
1027                     else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth4)
1028                         scanLineSize = cast(size_t)ceil((sampleSize * IHDR.width) / 2f);
1029                 }
1030 
1031                 totalSize = IHDR.width * pixelSampleSize * IHDR.height;
1032 
1033                 if (IHDR.filterMethod == PngIHDRFilter.Adaptive) {
1034                     scanLineSize += 1;
1035                     totalSize += IHDR.height;
1036                 }
1037                 
1038                 // no point calculating this if we are not gonna use it!
1039                 if (IHDR.interlaceMethod == PngIHDRInterlaceMethod.Adam7) {
1040                     if (IHDR.filterMethod == PngIHDRFilter.Adaptive)
1041                         myAdaptiveOffsets = alloc.makeArray!ubyte(IHDR.height);
1042                 
1043                     // calculates the length of each scan line per pass (Adam7)
1044                     for(pass = 0; pass < 7; pass++) {
1045                         if (starting_row[pass] >= IHDR.height) {
1046                             scanLinesSize[pass] = 0;
1047                         } else {
1048                             float tscanLineSize = IHDR.width * pixelSampleSize;
1049                             tscanLineSize -= starting_col[pass];
1050                             tscanLineSize /= col_increment[pass];
1051 
1052                             if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth1)
1053                                 tscanLineSize /= 8f;
1054                             else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth2)
1055                                 tscanLineSize /= 4f;
1056                             else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth4)
1057                                 tscanLineSize /= 2f;
1058 
1059                             tscanLineSize = ceil(tscanLineSize);
1060 
1061                             if (IHDR.filterMethod == PngIHDRFilter.Adaptive)
1062                                 tscanLineSize += 1;
1063 
1064                             if (tscanLineSize <= 1)
1065                                 tscanLineSize = 0;
1066 
1067                             scanLinesSize[pass] = cast(size_t)tscanLineSize;
1068                         }
1069                     }
1070                 }
1071                 
1072                 // decompress
1073                 decompressed = cast(ubyte[])uncompress(chunkData, totalSize);
1074 
1075                 void assignPixel(ColorP)(ColorP valuec) {
1076                     // store color at coordinate
1077                     static if (is(ColorP == Color))
1078                         value.setPixel(offsetX, offsetY, valuec);
1079                     else
1080                         value.setPixel(offsetX, offsetY, valuec.convertColor!Color);
1081 
1082                     // changes x and y coordinates for no interlace
1083                     if (IHDR.interlaceMethod == PngIHDRInterlaceMethod.NoInterlace) {
1084                         offsetX++;
1085                         
1086                         if (offsetX == IHDR.width) {
1087                             offsetX = 0;
1088                             offsetY++;
1089                         }
1090                     } else if (IHDR.interlaceMethod == PngIHDRInterlaceMethod.Adam7)
1091                         offsetX += col_increment[pass];
1092                 }
1093 
1094                 void grabPixelsFromScanLine(ubyte[] scanLine) {
1095                     bool useMultiByte = IHDR.bitDepth == PngIHDRBitDepth.BitDepth16;
1096                     
1097                     void handleSamples(ubyte[] samples) {
1098                         while(samples.length > 0) {
1099                             if (isPalette) {
1100                                     ubyte v = samples[0];
1101                                     if (v >= PLTE.colors.length)
1102                                         throw allocator.make!ImageNotLoadableException("IDAT unknown palette color");
1103 
1104                                     assignPixel(PLTE.colors[v]);
1105                                     samples = samples[1 .. $];
1106                             } else if (useMultiByte) {
1107                                 if (isColor) {
1108                                     ushort[4] values;
1109                                     values[0] = bigEndianToNative!ushort(cast(ubyte[2])samples[0 .. 2]);
1110                                     values[1] = bigEndianToNative!ushort(cast(ubyte[2])samples[2 .. 4]);
1111                                     values[2] = bigEndianToNative!ushort(cast(ubyte[2])samples[4 .. 6]);
1112                                     
1113                                     if (withAlpha) {
1114                                         values[3] = bigEndianToNative!ushort(cast(ubyte[2])samples[6 .. 8]);
1115                                         assignPixel(RGBA16(values[0], values[1], values[2], values[3]));
1116                                         samples = samples[8 .. $];
1117                                     } else {
1118                                         assignPixel(RGB16(values[0], values[1], values[2]));
1119                                         samples = samples[6 .. $];
1120                                     }
1121                                 } else if (isGrayScale) {
1122                                     ushort v = bigEndianToNative!ushort(cast(ubyte[2])samples[0 .. 2]);
1123                                     
1124                                     if (withAlpha) {
1125                                         assignPixel(RGBA16(v, v, v, bigEndianToNative!ushort(cast(ubyte[2])samples[2 .. 4])));
1126                                         samples = samples[4 .. $];
1127                                     } else {
1128                                         assignPixel(RGB16(v, v, v));
1129                                         samples = samples[2 .. $];
1130                                     }
1131                                 }
1132                             } else {
1133                                 if (isColor) {
1134                                     if (withAlpha) {
1135                                         assignPixel(RGBA8(samples[0], samples[1], samples[2], samples[3]));
1136                                         samples = samples[4 .. $];
1137                                     } else {
1138                                         assignPixel(RGB8(samples[0], samples[1], samples[2]));
1139                                         samples = samples[3 .. $];
1140                                     }
1141                                 } else if (isGrayScale) {
1142                                     ubyte v = samples[0];
1143                                     
1144                                     if (withAlpha) {
1145                                         assignPixel(RGBA8(v, v, v, samples[1]));
1146                                         samples = samples[2 .. $];
1147                                     } else {
1148                                         assignPixel(RGB8(v, v, v));
1149                                         samples = samples[1 .. $];
1150                                     }
1151                                 }
1152                             }
1153                         }
1154                     }
1155 
1156                     if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16 || IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
1157                         handleSamples(scanLine);
1158                     } else {
1159                         ptrdiff_t maxSamples = IHDR.width;
1160                         if (IHDR.interlaceMethod == PngIHDRInterlaceMethod.Adam7)
1161                             maxSamples = cast(size_t)ceil((maxSamples - starting_col[pass]) / cast(float)col_increment[pass]);
1162                         maxSamples *= sampleSize;
1163 
1164                         // 1, 2, 4 bit depths
1165                         foreach(scb; scanLine) {
1166                             ubyte[] samples;
1167                             
1168                             if (maxSamples <= 0)
1169                                 return;
1170                             
1171                             if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth4) {
1172                                 samples = tempBitDepth124[0 .. 2];
1173 
1174                                 samples[1] = cast(ubyte)((scb & 15) >> 0);
1175                                 samples[0] = cast(ubyte)((scb & 240) >> 4);
1176                                 
1177                                 if (!isPalette) {
1178                                     samples[0] *= 17;
1179                                     samples[1] *= 17;
1180                                 }
1181                             } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth2) {
1182                                 samples = tempBitDepth124[0 .. 4];
1183 
1184                                 samples[3] = cast(ubyte)((scb & 3) >> 0);
1185                                 samples[2] = cast(ubyte)((scb & 12) >> 2);
1186                                 samples[1] = cast(ubyte)((scb & 48) >> 4);
1187                                 samples[0] = cast(ubyte)((scb & 192) >> 6);
1188                                 
1189                                 if (!isPalette) {
1190                                     samples[0] *= 85;
1191                                     samples[1] *= 85;
1192                                     samples[2] *= 85;
1193                                     samples[3] *= 85;
1194                                 }
1195                             } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth1) {
1196                                 samples = tempBitDepth124[0 .. 8];
1197 
1198                                 samples[7] = cast(ubyte)((scb & 1) >> 0);
1199                                 samples[6] = cast(ubyte)((scb & 2) >> 1);
1200                                 samples[5] = cast(ubyte)((scb & 4) >> 2);
1201                                 samples[4] = cast(ubyte)((scb & 8) >> 3);
1202                                 samples[3] = cast(ubyte)((scb & 16) >> 4);
1203                                 samples[2] = cast(ubyte)((scb & 32) >> 5);
1204                                 samples[1] = cast(ubyte)((scb & 64) >> 6);
1205                                 samples[0] = cast(ubyte)((scb & 128) >> 7);
1206                                 
1207                                 if (!isPalette) {
1208                                     samples[0] *= 255;
1209                                     samples[1] *= 255;
1210                                     samples[2] *= 255;
1211                                     samples[3] *= 255;
1212                                     samples[4] *= 255;
1213                                     samples[5] *= 255;
1214                                     samples[6] *= 255;
1215                                     samples[7] *= 255;
1216                                 }
1217                             }
1218 
1219                             if (samples.length <= maxSamples)
1220                                 handleSamples(samples);
1221                             else
1222                                 handleSamples(samples[0 .. maxSamples]);
1223                             maxSamples -= samples.length;
1224                         }
1225                     }
1226                 }
1227 
1228                 // defilters the scan line
1229                 previousScanLine = null;
1230                 void scanLineDefilter(ubyte adaptiveOffset, ubyte[] scanLine) {
1231                     // defilter
1232                     if (IHDR.filterMethod == PngIHDRFilter.Adaptive) {
1233                         if (scanLine.length <= 0) {
1234                             previousScanLine = null;
1235                             return;
1236                         }
1237                         
1238                         foreach(i; 0 .. scanLine.length) {
1239                             switch(adaptiveOffset) {
1240                                 case 1: // sub
1241                                     // Sub(x) + Raw(x-bpp)
1242                                     
1243                                     if (i >= pixelPreviousByteAmount) {
1244                                         ubyte rawSub = scanLine[i-pixelPreviousByteAmount];
1245                                         scanLine[i] = cast(ubyte)(scanLine[i] + rawSub);
1246                                     } else {
1247                                         // no changes needed
1248                                     }
1249                                     
1250                                     break;
1251                                     
1252                                 case 2: // up
1253                                     // Up(x) + Prior(x)
1254                                     
1255                                     if (previousScanLine.length > i) {
1256                                         ubyte prior = previousScanLine[i];
1257                                         scanLine[i] = cast(ubyte)(scanLine[i] + prior);
1258                                     } else {
1259                                         // no changes needed
1260                                     }
1261                                     break;
1262                                     
1263                                 case 3: // average
1264                                     // Average(x) + floor((Raw(x-bpp)+Prior(x))/2)
1265                                     
1266                                     if (previousScanLine.length > i) {
1267                                         if (i >= pixelPreviousByteAmount) {
1268                                             ubyte prior = previousScanLine[i];
1269                                             ubyte rawSub = scanLine[i-pixelPreviousByteAmount];
1270                                             scanLine[i] = cast(ubyte)(scanLine[i] + floor(cast(real)(rawSub + prior) / 2f));
1271                                         } else {
1272                                             ubyte prior = previousScanLine[i];
1273                                             ubyte rawSub = 0;
1274                                             scanLine[i] = cast(ubyte)(scanLine[i] + floor(cast(real)(rawSub + prior) / 2f));
1275                                         }
1276                                     } else if (i >= pixelPreviousByteAmount) {
1277                                         ubyte prior = 0;
1278                                         ubyte rawSub = scanLine[i-pixelPreviousByteAmount];
1279                                         scanLine[i] = cast(ubyte)(scanLine[i] + floor(cast(real)(rawSub + prior) / 2f));
1280                                     } else {
1281                                         // no changes needed
1282                                     }
1283                                     break;
1284                                     
1285                                 case 4: // paeth
1286                                     //  Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp))
1287                                     
1288                                     if (previousScanLine.length > i) {
1289                                         if (i >= pixelPreviousByteAmount) {
1290                                             ubyte prior = previousScanLine[i];
1291                                             ubyte rawSub = scanLine[i-pixelPreviousByteAmount];
1292                                             ubyte priorRawSub = previousScanLine[i-pixelPreviousByteAmount];
1293                                             scanLine[i] = cast(ubyte)(scanLine[i] + PaethPredictor(rawSub, prior, priorRawSub));
1294                                         } else {
1295                                             ubyte prior = previousScanLine[i];
1296                                             ubyte rawSub = 0;
1297                                             ubyte priorRawSub = 0;
1298                                             scanLine[i] = cast(ubyte)(scanLine[i] + PaethPredictor(rawSub, prior, priorRawSub));
1299                                         }
1300                                     } else if (i >= pixelPreviousByteAmount) {
1301                                         ubyte prior = 0;
1302                                         ubyte rawSub = scanLine[i-pixelPreviousByteAmount];
1303                                         ubyte priorRawSub = 0;
1304                                         scanLine[i] = cast(ubyte)(scanLine[i] + PaethPredictor(rawSub, prior, priorRawSub));
1305                                     } else {
1306                                         // no changes needed
1307                                     }
1308                                     
1309                                     break;
1310                                     
1311                                 default:
1312                                 case 0: // none
1313                                     break;
1314                             }
1315                         }
1316                     }
1317 
1318                     previousScanLine = scanLine;
1319                     grabPixelsFromScanLine(scanLine);
1320                 }
1321 
1322                 size_t lastYStart = size_t.max;
1323 
1324                 // performs the actual parsing of the scanlines
1325                 if (IHDR.interlaceMethod == PngIHDRInterlaceMethod.Adam7) {
1326                     pass = 0;
1327                     
1328                     while(pass < 7) {
1329                         offsetY = starting_row[pass];
1330                         scanLineSize = scanLinesSize[pass];
1331                         
1332                         if (scanLineSize == 0) {
1333                             pass++;
1334                             continue;
1335                         }
1336                         
1337                         while(offsetY < IHDR.height) {
1338                             offsetX = starting_col[pass];
1339                             bool thisScanLine = IHDR.bitDepth != PngIHDRBitDepth.BitDepth1 || !(offsetY == lastYStart && offsetX > 0);
1340                             
1341                             if (IHDR.filterMethod == PngIHDRFilter.Adaptive) {
1342                                 if (thisScanLine) {
1343                                     myAdaptiveOffsets[offsetY] = decompressed[offset];
1344                                     scanLineDefilter(myAdaptiveOffsets[offsetY], decompressed[offset + 1 .. offset + scanLineSize]);
1345                                     offset += scanLineSize;
1346                                 } else {
1347                                     scanLineDefilter(myAdaptiveOffsets[offsetY], decompressed[offset .. offset + (scanLineSize - 1)]);
1348                                     offset += (scanLineSize - 1);
1349                                 }
1350                             } else {
1351                                 assert(0);
1352                             }
1353                             
1354                             lastYStart = offsetY;
1355                             offsetY += row_increment[pass];
1356                         }
1357 
1358                         pass++;
1359                     }
1360                 } else {
1361                     while(offset < decompressed.length) {
1362                         if (IHDR.filterMethod == PngIHDRFilter.Adaptive) {
1363                             scanLineDefilter(decompressed[offset], decompressed[offset + 1 .. offset + scanLineSize]);
1364                         } else {
1365                             assert(0);
1366                         }
1367                         offset += scanLineSize;
1368                     }
1369                 }
1370                 
1371                 alloc.dispose(tempBitDepth124);
1372             }
1373         }
1374         
1375         void readChunk_IEND(ubyte[] chunkData) @safe {
1376             // IEND chunk should be the last one.
1377             // It doesn't do anything special other then say, this is the end.
1378             // Now stop looking for more chunks!
1379         }
1380         
1381         /*
1382          * The exporter
1383          */
1384         
1385         managed!(ubyte[]) performExport() @trusted {
1386             import std.digest.crc : crc32Of;
1387             ubyte[] buffer = allocator.makeArray!ubyte((1024 * 1024 * 8) + 4); // 8mb
1388 			assert(buffer.length > 0);
1389 
1390             import core.memory : GC;
1391             GC.disable;
1392             
1393             ubyte[] ret = allocator.makeArray!ubyte(8);
1394             ret[0 .. 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
1395             
1396             void writeChunk(char[4] name, ubyte[] data) @trusted {
1397                 size_t len = data.length + 12; // name + length + crc
1398                 allocator.expandArray(ret, len);
1399 
1400                 ret[$-len .. $][0 .. 4] = nativeToBigEndian(cast(uint)data.length);
1401                 ret[$-len .. $][4 .. 8] = cast(ubyte[4])name[];
1402                 ret[$-len .. $][8 .. $-4] = data[];
1403                 
1404                 buffer[0 .. 4] = cast(ubyte[4])name[];
1405                 ret[$-4 .. $] = nativeToBigEndian(*cast(uint*)crc32Of(buffer[0 .. data.length + 4]).ptr);
1406             }
1407             
1408             writeChunk_IHDR(buffer[4 .. $], &writeChunk);
1409             if (gAMA !is null)
1410                 writeChunk_gAMA(buffer[4 .. $], &writeChunk);
1411             if (PLTE !is null)
1412                 writeChunk_PLTE(buffer[4 .. $], &writeChunk);
1413             if (tRNS !is null)
1414                 writeChunk_tRNS(buffer[4 .. $], &writeChunk);
1415             if (cHRM !is null)
1416                 writeChunk_cHRM(buffer[4 .. $], &writeChunk);
1417             if (sRGB !is null)
1418                 writeChunk_sRGB(buffer[4 .. $], &writeChunk);
1419             if (iCCP !is null)
1420                 writeChunk_iCCP(buffer[4 .. $], &writeChunk);
1421                 
1422             writeChunk_tEXt(buffer[4 .. $], &writeChunk);
1423             writeChunk_zEXt(buffer[4 .. $], &writeChunk);
1424 
1425             if (bKGD !is null)
1426                 writeChunk_bKGD(buffer[4 .. $], &writeChunk);
1427             if (pPHs !is null)
1428                 writeChunk_pPHs(buffer[4 .. $], &writeChunk);
1429             if (sBIT !is null)
1430                 writeChunk_sBIT(buffer[4 .. $], &writeChunk);
1431             if (sPLT.length > 0)
1432                 writeChunk_sPLT(buffer[4 .. $], &writeChunk);
1433             if (hIST.length > 0)
1434                 writeChunk_hIST(buffer[4 .. $], &writeChunk);
1435             if (tIME !is null)
1436                 writeChunk_tIME(buffer[4 .. $], &writeChunk);
1437 
1438             static if (!is(Color == HeadersOnly)) {
1439                 writeChunk_IDAT(buffer[4 .. $], &writeChunk);
1440             }
1441 
1442             // it contains nothing, so why bother having a dedicated method?
1443             writeChunk(cast(char[4])"IEND", null);
1444             
1445 			GC.enable;
1446 			allocator.dispose(buffer);
1447             return managed!(ubyte[])(ret, managers(ReferenceCountedManager()), alloc);
1448         }
1449         
1450         void writeChunk_IHDR(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1451             ubyte[] towrite;
1452             
1453             towrite = buffer[0 .. 13];
1454             towrite[0 .. 4] = nativeToBigEndian(IHDR.width);
1455             towrite[4 .. 8] = nativeToBigEndian(IHDR.height);
1456             towrite[8 .. 9] = nativeToBigEndian(IHDR.bitDepth);
1457             towrite[9 .. 10] = nativeToBigEndian(IHDR.colorType);
1458             towrite[10 .. 11] = nativeToBigEndian(IHDR.compressionMethod);
1459             towrite[11 .. 12] = nativeToBigEndian(IHDR.filterMethod);
1460             towrite[12 .. 13] = nativeToBigEndian(IHDR.interlaceMethod);
1461             
1462             write(cast(char[4])"IHDR", towrite);
1463         }
1464         
1465         void writeChunk_PLTE(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1466             ubyte[] towrite;
1467             
1468             towrite = buffer[0 .. PLTE.colors.length * 3];
1469 
1470             size_t offset;
1471             foreach(i, c; PLTE.colors) {
1472                 towrite[offset] = c.r.value;
1473                 towrite[offset + 1] = c.g.value;
1474                 towrite[offset + 2] = c.b.value;
1475 
1476                 offset += 3;
1477             }
1478 
1479             write(cast(char[4])"PLTE", towrite);
1480         }
1481         
1482         void writeChunk_tRNS(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1483             ubyte[] towrite;
1484             
1485             if (IHDR.colorType & PngIHDRColorType.Palette) {
1486                 towrite = buffer[0 .. tRNS.indexAlphas.length];
1487                 towrite[] = tRNS.indexAlphas[];
1488             } else if (IHDR.colorType & PngIHDRColorType.Grayscale) {
1489                 if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
1490                     towrite = buffer[0 .. 2];
1491 					towrite[] = nativeToBigEndian(tRNS.b16.r.value);
1492                 } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
1493                     towrite = buffer[0 .. 2];
1494 					towrite[0] = tRNS.b8.r.value;
1495                 } else {
1496                     towrite = buffer[0 .. 2];
1497 					towrite[0] = cast(ubyte)(tRNS.b8.r.value / cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1));
1498                 }
1499             } else if (IHDR.colorType & PngIHDRColorType.ColorUsed) {
1500                 if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
1501                     towrite = buffer[0 .. 6];
1502                     towrite[0 .. 2] = nativeToBigEndian(tRNS.b16.r.value);
1503 					towrite[2 .. 4] = nativeToBigEndian(tRNS.b16.g.value);
1504 					towrite[4 .. 6] = nativeToBigEndian(tRNS.b16.b.value);
1505                 } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
1506                     towrite = buffer[0 .. 6];
1507 					towrite[0] = tRNS.b8.r.value;
1508 					towrite[2] = tRNS.b8.g.value;
1509 					towrite[4] = tRNS.b8.b.value;
1510                 } else {
1511                     towrite = buffer[0 .. 6];
1512 					towrite[0] = cast(ubyte)(tRNS.b8.r.value / cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1));
1513 					towrite[2] = cast(ubyte)(tRNS.b8.g.value / cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1));
1514 					towrite[4] = cast(ubyte)(tRNS.b8.b.value / cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1));
1515                 }
1516             }
1517             
1518             write(cast(char[4])"tRNS", towrite);
1519         }
1520         
1521         void writeChunk_gAMA(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1522             ubyte[] towrite = buffer[0 .. 4];
1523             towrite[] = nativeToBigEndian(gAMA.value);
1524             write(cast(char[4])"gAMA", towrite);
1525         }
1526         
1527         void writeChunk_cHRM(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1528             ubyte[] towrite = buffer[0 .. 32];
1529             towrite[0 .. 4] = nativeToBigEndian(cHRM.white_x);
1530             towrite[4 .. 8] = nativeToBigEndian(cHRM.white_y);
1531             towrite[8 .. 12] = nativeToBigEndian(cHRM.red_x);
1532             towrite[12 .. 16] = nativeToBigEndian(cHRM.red_y);
1533             towrite[16 .. 20] = nativeToBigEndian(cHRM.green_x);
1534             towrite[20 .. 24] = nativeToBigEndian(cHRM.green_y);
1535             towrite[24 .. 28] = nativeToBigEndian(cHRM.blue_x);
1536             towrite[28 .. 32] = nativeToBigEndian(cHRM.blue_y);
1537             write(cast(char[4])"cHRM", towrite);
1538         }
1539         
1540         void writeChunk_sRGB(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1541             ubyte[] towrite = buffer[0 .. 1];
1542             towrite[0] = sRGB.intent;
1543             write(cast(char[4])"sRGB", towrite);
1544         }
1545         
1546         void writeChunk_iCCP(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1547             import std.zlib : compress;
1548             ubyte[] towrite;
1549             
1550             towrite = buffer[0 .. iCCP.profileName.length + 2];
1551             towrite[0 .. $-2] = cast(ubyte[])iCCP.profileName[];
1552             towrite[$-2] = '\0';
1553             towrite[$-1] = cast(ubyte)iCCP.compressionMethod;
1554             
1555             ubyte[] compressed = cast(ubyte[])compress(iCCP.profile);
1556             towrite = buffer[0 .. towrite.length + compressed.length];
1557             towrite[$-compressed.length .. $] = compressed[];
1558             
1559             write(cast(char[4])"iCCP", towrite);
1560         }
1561         
1562         void writeChunk_tEXt(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1563             foreach(const(PngTextKeywords) keyword, string value; *tEXt) {
1564                 ubyte[] towrite = buffer[0 .. keyword.length + 1];
1565                 
1566                 towrite[0 .. $-1] = cast(ubyte[])keyword[];
1567                 towrite[$-1] = '\0';
1568                 
1569                 towrite = buffer[0 .. towrite.length + value.length];
1570                 towrite[$-value.length .. $] = cast(ubyte[])value[];
1571                 
1572                 write(cast(char[4])"tEXt", towrite);
1573             }
1574         }
1575         
1576         void writeChunk_zEXt(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1577             import std.zlib : compress;
1578             
1579 			foreach(const(PngTextKeywords) keyword, string value; *zEXt) {
1580                 ubyte[] towrite = buffer[0 .. keyword.length + 2];
1581                 
1582                 towrite[0 .. $-2] = cast(ubyte[])keyword[];
1583                 towrite[$-2] = '\0';
1584                 towrite[$-1] = PngIHDRCompresion.DeflateInflate;
1585                 
1586                 // FIXME: allocates
1587                 ubyte[] compressed = cast(ubyte[])compress(value);
1588                 
1589                 towrite = buffer[0 .. towrite.length + compressed.length];
1590                 towrite[$-compressed.length .. $] = compressed[];
1591                 
1592                 write(cast(char[4])"zEXt", towrite);
1593             }
1594         }
1595         
1596         void writeChunk_bKGD(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1597             ubyte[] towrite;
1598             
1599             if (IHDR.colorType & PngIHDRColorType.Palette) {
1600                 towrite = buffer[0 .. 1];
1601                 towrite[0] = bKGD.index;
1602             } else if (IHDR.colorType & PngIHDRColorType.Grayscale) {
1603                 if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
1604                     towrite = buffer[0 .. 2];
1605 					towrite[] = nativeToBigEndian(bKGD.b16.r.value);
1606                 } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
1607                     towrite = buffer[0 .. 2];
1608 					towrite[0] = bKGD.b8.r.value;
1609                 } else {
1610                     towrite = buffer[0 .. 2];
1611 					towrite[0] = cast(ubyte)(bKGD.b8.r.value / cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1));
1612                 }
1613             } else if (IHDR.colorType & PngIHDRColorType.ColorUsed) {
1614                 if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
1615                     towrite = buffer[0 .. 6];
1616 					towrite[0 .. 2] = nativeToBigEndian(bKGD.b16.r.value);
1617 					towrite[2 .. 4] = nativeToBigEndian(bKGD.b16.g.value);
1618 					towrite[4 .. 6] = nativeToBigEndian(bKGD.b16.b.value);
1619                 } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
1620                     towrite = buffer[0 .. 6];
1621 					towrite[0] = bKGD.b8.r.value;
1622 					towrite[2] = bKGD.b8.g.value;
1623 					towrite[4] = bKGD.b8.b.value;
1624                 } else {
1625                     towrite = buffer[0 .. 6];
1626 					towrite[0] = cast(ubyte)(bKGD.b8.r.value / cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1));
1627 					towrite[2] = cast(ubyte)(bKGD.b8.g.value / cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1));
1628 					towrite[4] = cast(ubyte)(bKGD.b8.b.value / cast(ubyte)(256f/(2^(cast(ubyte)IHDR.bitDepth))-1));
1629                 }
1630             }
1631             
1632             write(cast(char[4])"bKGD", towrite);
1633         }
1634         
1635         void writeChunk_pPHs(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1636             ubyte[] towrite = buffer[0 .. 9];
1637             
1638             towrite[0 .. 4] = nativeToBigEndian(pPHs.ppx);
1639             towrite[4 .. 8] = nativeToBigEndian(pPHs.ppy);
1640             towrite[8] = pPHs.unit;
1641             
1642             write(cast(char[4])"pPHs", towrite);
1643         }
1644         
1645         void writeChunk_sBIT(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1646             ubyte[] towrite;
1647             
1648             if (IHDR.colorType & PngIHDRColorType.Palette) {
1649                 towrite = buffer[0 .. 3];
1650                 towrite[] = sBIT.indexed[];
1651             } else if (IHDR.colorType & PngIHDRColorType.Grayscale) {
1652                 bool withAlpha = (IHDR.colorType & PngIHDRColorType.AlphaChannelUsed) == PngIHDRColorType.AlphaChannelUsed;
1653                 
1654                 if (withAlpha) {
1655                     towrite = buffer[0 .. 2];
1656                     towrite[] = sBIT.grayScaleAlpha[];
1657                 } else {
1658                     towrite = buffer[0 .. 1];
1659                     towrite[0] = sBIT.grayScale;
1660                 }
1661             } else if (IHDR.colorType & PngIHDRColorType.ColorUsed) {
1662                 bool withAlpha = (IHDR.colorType & PngIHDRColorType.AlphaChannelUsed) == PngIHDRColorType.AlphaChannelUsed;
1663                 
1664                 if (withAlpha) {
1665                     towrite = buffer[0 .. 4];
1666                     towrite[] = sBIT.trueColorAlpha[];
1667                 } else {
1668                     towrite = buffer[0 .. 3];
1669                     towrite[] = sBIT.trueColor[];
1670                 }
1671             }
1672             
1673             write(cast(char[4])"sBIT", towrite);
1674         }
1675         
1676         void writeChunk_sPLT(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1677             ubyte[] towrite;
1678             
1679             foreach(chunk; *sPLT) {
1680                 towrite = buffer[towrite.length .. towrite.length + chunk.paletteName.length + 2];
1681                 towrite[$-(chunk.paletteName.length + 2) .. $-2] = cast(ubyte[])chunk.paletteName[];
1682                 towrite[$-2] = '\0';
1683                 towrite[$-1] = chunk.sampleDepth;
1684                 
1685                 size_t count;
1686                 if (chunk.sampleDepth == PngIHDRBitDepth.BitDepth8) {
1687                     size_t offset = towrite.length;
1688                     towrite = buffer[0 .. towrite.length + (chunk.colors.length * 6)];
1689                     
1690                     foreach(c; chunk.colors) {
1691 						towrite[offset] = c.color.b8.r.value;
1692 						towrite[offset + 1] = c.color.b8.g.value;
1693 						towrite[offset + 2] = c.color.b8.b.value;
1694 						towrite[offset + 3] = c.color.b8.a.value;
1695                         towrite[offset + 4 .. offset + 6] = nativeToBigEndian(c.frequency);
1696                         
1697                         offset += 6;
1698                     }
1699                 } else if (chunk.sampleDepth == PngIHDRBitDepth.BitDepth16) {
1700                     size_t offset = towrite.length;
1701                     towrite = buffer[0 .. towrite.length + (chunk.colors.length * 10)];
1702                     
1703                     foreach(c; chunk.colors) {
1704 						towrite[offset .. offset + 2] = nativeToBigEndian(c.color.b16.r.value);
1705 						towrite[offset + 2 .. offset + 4] = nativeToBigEndian(c.color.b16.g.value);
1706 						towrite[offset + 4 .. offset + 6] = nativeToBigEndian(c.color.b16.b.value);
1707 						towrite[offset + 6 .. offset + 8] = nativeToBigEndian(c.color.b16.a.value);
1708                         towrite[offset + 8 .. offset + 10] = nativeToBigEndian(c.frequency);
1709                         
1710                         offset += 10;
1711                     }
1712                 } else {
1713                     // TODO: ugh oh, this is not good!
1714                 }
1715             }
1716             
1717             write(cast(char[4])"sPLT", towrite);
1718         }
1719         
1720         void writeChunk_hIST(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1721             ubyte[] towrite = buffer[0 .. hIST.length * 2];
1722             
1723             size_t offset;
1724             foreach(v; *hIST) {
1725                 towrite[offset .. offset + 2] = nativeToBigEndian(v);
1726                 
1727                 offset += 2;
1728             }
1729             
1730             write(cast(char[4])"hIST", towrite);
1731         }
1732         
1733         void writeChunk_tIME(ubyte[] buffer, void delegate(char[4], ubyte[]) write) @trusted {
1734             ubyte[] towrite = buffer[0 .. 7];
1735             
1736             towrite[0 .. 2] = nativeToBigEndian(tIME.year);
1737             towrite[2] = tIME.month;
1738             towrite[3] = tIME.day;
1739             towrite[4] = tIME.hour;
1740             towrite[5] = tIME.minute;
1741             towrite[6] = tIME.second;
1742             
1743             write(cast(char[4])"tIME", towrite);
1744         }
1745         
1746         static if (!is(Color == HeadersOnly)) {
1747             void writeChunk_IDAT(ubyte[] buffer, void delegate(char[4], ubyte[]) theWriteFunc) @trusted {
1748                 import std.zlib : compress;
1749                 import std.math : ceil, floor;
1750 
1751                 ubyte findPLTEColor(Color c1) {
1752                     RGB8 c = convertColor!RGB8(c1);
1753                     
1754                     foreach(i, c2; PLTE.colors) {
1755                         if (i >= 256)
1756                             break;
1757                         
1758                         if (c2 == c) {
1759                             return cast(ubyte)i;
1760                         }
1761                     }
1762                     
1763                     throw alloc.make!ImageNotExportableException("Palette not completed with all colors.");
1764                 }
1765 
1766                 // a simple check
1767                 if (IHDR.interlaceMethod == PngIHDRInterlaceMethod.Adam7 || IHDR.interlaceMethod == PngIHDRInterlaceMethod.NoInterlace)
1768                 {} else
1769                     throw allocator.make!ImageNotLoadableException("IDAT unknown interlace method");
1770 
1771                 if (IHDR.compressionMethod == PngIHDRCompresion.DeflateInflate) {}
1772                 else
1773                     throw allocator.make!ImageNotLoadableException("IDAT unknown compression method");
1774 
1775                 // constants
1776                 size_t pixelPreviousByteAmount, rowSize;
1777                 ubyte sampleSize, pixelSampleSize, scanLineFilterMethodOffset;
1778 
1779                 bool withAlpha = (IHDR.colorType & PngIHDRColorType.AlphaChannelUsed) == PngIHDRColorType.AlphaChannelUsed;
1780                 bool isGrayScale = (IHDR.colorType & PngIHDRColorType.Grayscale) == PngIHDRColorType.Grayscale;
1781                 bool isPalette = (IHDR.colorType & PngIHDRColorType.Palette) == PngIHDRColorType.Palette;
1782                 bool isColor = (IHDR.colorType & PngIHDRColorType.ColorUsed) == PngIHDRColorType.ColorUsed;
1783 
1784                 // some needed variables, in future processing
1785                 ubyte[] decompressed, previousScanLine, tempFilterPrevious, tempFilterCurrent, tempScanLine, currentScanLine;
1786                 ubyte pass, byteToOffset;
1787                 size_t offsetX, offsetY;
1788                 
1789                 final switch(IHDR.colorType) {
1790                     case PngIHDRColorType.AlphaChannelUsed:
1791                         sampleSize = 2;
1792                         break;
1793                     case PngIHDRColorType.PalletteWithColorUsed:
1794                     case PngIHDRColorType.Palette:
1795                     case PngIHDRColorType.Grayscale:
1796                         sampleSize = 1;
1797                         break;
1798                     case PngIHDRColorType.ColorUsedWithAlpha:
1799                         sampleSize = 4;
1800                         break;
1801                     case PngIHDRColorType.ColorUsed:
1802                         sampleSize = 3;
1803                         break;
1804                 }
1805 
1806                 if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
1807                     pixelSampleSize = cast(ubyte)(sampleSize + sampleSize);
1808                     pixelPreviousByteAmount = pixelSampleSize;
1809                 } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
1810                     pixelSampleSize = sampleSize;
1811                     pixelPreviousByteAmount = sampleSize;
1812                 } else {
1813                     pixelSampleSize = sampleSize;
1814                     pixelPreviousByteAmount = 1;
1815                 }
1816 
1817                 rowSize = IHDR.width * pixelSampleSize;
1818                 if (IHDR.filterMethod == PngIHDRFilter.Adaptive)
1819                     rowSize++;
1820 
1821                 tempFilterPrevious = alloc.makeArray!ubyte(rowSize);
1822                 tempFilterCurrent = alloc.makeArray!ubyte(rowSize);
1823                 tempScanLine = alloc.makeArray!ubyte(rowSize);
1824 
1825                 // filters the scan line
1826                 previousScanLine = null;
1827                 void filterScanLine(ubyte[] scanLine) {
1828                     // filter
1829                     tempFilterCurrent[0 .. scanLine.length] = scanLine[];
1830 
1831                     if (IHDR.filterMethod == PngIHDRFilter.Adaptive) {
1832                         if (scanLine.length <= 1) {
1833                             previousScanLine = tempFilterPrevious[0 .. 0];
1834                             return;
1835                         }
1836                         ubyte adaptiveOffset = scanLine[0];
1837 
1838                         foreach(i; 1 .. scanLine.length) {
1839                             switch(adaptiveOffset) {
1840                                 case 1: // sub
1841                                     // Sub(x) - Raw(x-bpp)
1842                                     
1843                                     if (i-1 >= pixelPreviousByteAmount) {
1844                                         ubyte rawSub = tempFilterCurrent[i-pixelPreviousByteAmount];
1845                                         scanLine[i] = cast(ubyte)(scanLine[i] - rawSub);
1846                                     } else {
1847                                         // no changes needed
1848                                     }
1849                                     break;
1850                                     
1851                                 case 2: // up
1852                                     // Up(x) - Prior(x)
1853                                     
1854                                     if (previousScanLine.length > i) {
1855                                         ubyte prior = previousScanLine[i];
1856                                         scanLine[i] = cast(ubyte)(scanLine[i] - prior);
1857                                     } else {
1858                                         // no changes needed
1859                                     }
1860                                     break;
1861                                     
1862                                 case 3: // average
1863                                     // Average(x) - floor((Raw(x-bpp)+Prior(x))/2)
1864                                     
1865                                     if (previousScanLine.length > i) {
1866                                         if (i-1 >= pixelPreviousByteAmount) {
1867                                             ubyte prior = previousScanLine[i];
1868                                             ubyte rawSub = tempFilterCurrent[i-pixelPreviousByteAmount];
1869                                             scanLine[i] = cast(ubyte)(scanLine[i] - floor(cast(real)(rawSub + prior) / 2f));
1870                                         } else {
1871                                             ubyte prior = previousScanLine[i];
1872                                             ubyte rawSub = 0;
1873                                             scanLine[i] = cast(ubyte)(scanLine[i] - floor(cast(real)(rawSub + prior) / 2f));
1874                                         }
1875                                     } else if (i-1 >= pixelPreviousByteAmount) {
1876                                         ubyte prior = 0;
1877                                         ubyte rawSub = tempFilterCurrent[i-pixelPreviousByteAmount];
1878                                         scanLine[i] = cast(ubyte)(scanLine[i] - floor(cast(real)(rawSub + prior) / 2f));
1879                                     } else {
1880                                         // no changes needed
1881                                     }
1882                                     break;
1883                                     
1884                                 case 4: // paeth
1885                                     //  Paeth(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp))
1886                                     
1887                                     if (previousScanLine.length > i) {
1888                                         if (i-1 >= pixelPreviousByteAmount) {
1889                                             ubyte prior = previousScanLine[i];
1890                                             ubyte rawSub = tempFilterCurrent[i-pixelPreviousByteAmount];
1891                                             ubyte priorRawSub = previousScanLine[i-pixelPreviousByteAmount];
1892                                             scanLine[i] = cast(ubyte)(scanLine[i] - PaethPredictor(rawSub, prior, priorRawSub));
1893                                         } else {
1894                                             ubyte prior = previousScanLine[i];
1895                                             ubyte rawSub = 0;
1896                                             ubyte priorRawSub = 0;
1897                                             scanLine[i] = cast(ubyte)(scanLine[i] - PaethPredictor(rawSub, prior, priorRawSub));
1898                                         }
1899                                     } else if (i-1 >= pixelPreviousByteAmount) {
1900                                         ubyte prior = 0;
1901                                         ubyte rawSub = tempFilterCurrent[i-pixelPreviousByteAmount];
1902                                         ubyte priorRawSub = 0;
1903                                         scanLine[i] = cast(ubyte)(scanLine[i] - PaethPredictor(rawSub, prior, priorRawSub));
1904                                     } else {
1905                                         // no changes needed
1906                                     }
1907                                     break;
1908                                     
1909                                 default:
1910                                 case 0: // none
1911                                     break;
1912                             }
1913                         }
1914                     }
1915 
1916 					if (decompressed is null) {
1917 						decompressed = alloc.makeArray!ubyte(scanLine.length-scanLineFilterMethodOffset);
1918 					} else {
1919 						alloc.expandArray(decompressed, scanLine.length-scanLineFilterMethodOffset);
1920 					}
1921                     decompressed[$-(scanLine.length - scanLineFilterMethodOffset) .. $] = scanLine[scanLineFilterMethodOffset .. $];
1922 
1923                     previousScanLine = tempFilterPrevious[0 .. scanLine.length];
1924                     previousScanLine[] = tempFilterCurrent[0 .. scanLine.length][];
1925                 }
1926                 
1927                 const ubyte bitByteCount = cast(ubyte)(8 / IHDR.bitDepth);
1928 
1929                 void storeChannel(ubyte v) {
1930                     if (byteToOffset == bitByteCount)
1931                         byteToOffset = 0;
1932                     if (byteToOffset == 0)
1933                         currentScanLine = tempScanLine[0 .. currentScanLine.length + 1];
1934 
1935                     if (byteToOffset > 0) {
1936                         v = cast(ubyte)(v << ((8-IHDR.bitDepth)-(IHDR.bitDepth * byteToOffset)));
1937                         currentScanLine[$-1] |= v;
1938                     } else
1939                         currentScanLine[$-1] = cast(ubyte)(v << (8-IHDR.bitDepth));
1940                     
1941                     byteToOffset++;
1942                 }
1943                 
1944                 void serializeScanLine(Color c) {
1945                     // serailize the color as apropriete form into the scanLineBuffer
1946 
1947                     if (isPalette) {
1948                         if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8 || IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
1949                             currentScanLine = tempScanLine[0 .. currentScanLine.length + 1];
1950                             currentScanLine[$-1] = findPLTEColor(c);
1951                         } else {
1952                            storeChannel(findPLTEColor(c));
1953                         }
1954                     } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8 || IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
1955                         currentScanLine = tempScanLine[0 .. currentScanLine.length + pixelSampleSize];
1956 
1957                         if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth16) {
1958                             RGBA16 pixToUse = convertColor!RGBA16(c);
1959                             
1960                             if (isColor) {
1961                                 if (withAlpha) {
1962                                     currentScanLine[$-8 .. $-6] = nativeToBigEndian(pixToUse.r.value);
1963 									currentScanLine[$-6 .. $-4] = nativeToBigEndian(pixToUse.g.value);
1964 									currentScanLine[$-4 .. $-2] = nativeToBigEndian(pixToUse.b.value);
1965 									currentScanLine[$-2 .. $] = nativeToBigEndian(pixToUse.a.value);
1966                                 } else {
1967                                     currentScanLine[$-6 .. $-4] = nativeToBigEndian(pixToUse.r.value);
1968                                     currentScanLine[$-4 .. $-2] = nativeToBigEndian(pixToUse.g.value);
1969                                     currentScanLine[$-2 .. $] = nativeToBigEndian(pixToUse.b.value);
1970                                 }
1971                             } else if (isGrayScale) {
1972                                 float pixG = (pixToUse.r.value / 3f) + (pixToUse.g.value / 3f) + (pixToUse.b.value / 3f);
1973                                 
1974                                 if (withAlpha) {
1975                                     currentScanLine[$-4 .. $-2] = nativeToBigEndian(cast(ushort)pixG);
1976                                     currentScanLine[$-2 .. $] = nativeToBigEndian(pixToUse.a.value);
1977                                 } else {
1978                                     currentScanLine[$-2 .. $] = nativeToBigEndian(cast(ushort)pixG);
1979                                 }
1980                             }
1981                         } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth8) {
1982                             RGBA8 pixToUse = convertColor!RGBA8(c);
1983                             
1984                             if (isColor) {
1985                                 if (withAlpha) {
1986                                     currentScanLine[$-4 .. $-3] = pixToUse.r.value;
1987                                     currentScanLine[$-3 .. $-2] = pixToUse.g.value;
1988                                     currentScanLine[$-2 .. $-1] = pixToUse.b.value;
1989                                     currentScanLine[$-1 .. $] = pixToUse.a.value;
1990                                 } else {
1991                                     currentScanLine[$-3 .. $-2] = pixToUse.r.value;
1992                                     currentScanLine[$-2 .. $-1] = pixToUse.g.value;
1993                                     currentScanLine[$-1 .. $] = pixToUse.b.value;
1994                                 }
1995                             } else if (isGrayScale) {
1996                                 float pixG = (pixToUse.r.value / 3f) + (pixToUse.g.value / 3f) + (pixToUse.b.value / 3f);
1997                                 
1998                                 if (withAlpha) {
1999                                     currentScanLine[$-2 .. $-1] = cast(ubyte)pixG;
2000                                     currentScanLine[$-1 .. $] = pixToUse.a.value;
2001                                 } else {
2002                                     currentScanLine[$-1 .. $] = cast(ubyte)pixG;
2003                                 }
2004                             }
2005                         }
2006                     } else {
2007                         // 1, 2, 4 bit depths
2008                         ubyte bitMaxValue;
2009                         ubyte samplesPerPixel;
2010 
2011                         if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth4) {
2012                             bitMaxValue = 15;
2013                         } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth2) {
2014                             bitMaxValue = 3;
2015                         } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth1) {
2016                             bitMaxValue = 1;
2017                         }
2018                         
2019                         if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth4) {
2020                             samplesPerPixel = 2;
2021                         } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth2) {
2022                             samplesPerPixel = 4;
2023                         } else if (IHDR.bitDepth == PngIHDRBitDepth.BitDepth1) {
2024                             samplesPerPixel = 8;
2025                         } else
2026                             samplesPerPixel = 1;
2027                         
2028                         RGBA8 pixToUse = convertColor!RGBA8(c);
2029                         ubyte[4] bitDepthValues = [
2030                             cast(ubyte)ceil((pixToUse.r.value / 256f) * bitMaxValue),
2031                             cast(ubyte)ceil((pixToUse.g.value / 256f) * bitMaxValue),
2032                             cast(ubyte)ceil((pixToUse.b.value / 256f) * bitMaxValue),
2033                             cast(ubyte)ceil((pixToUse.a.value / 256f) * bitMaxValue)
2034                         ];
2035                         
2036                         if (isColor) {
2037                             storeChannel(bitDepthValues[0]);
2038                             storeChannel(bitDepthValues[1]);
2039                             storeChannel(bitDepthValues[2]);
2040                             
2041                             if (withAlpha)
2042                                 storeChannel(bitDepthValues[3]);
2043                         } else if (isGrayScale) {
2044                             storeChannel(bitDepthValues[0]);
2045                             
2046                             if (withAlpha)
2047                                 storeChannel(bitDepthValues[3]);
2048                         }
2049                     }
2050                 }
2051 
2052                 void startScanLine() {
2053                     if (IHDR.filterMethod == PngIHDRFilter.Adaptive) {
2054                         const ubyte[] filtersToApply = [cast(ubyte)0, 1];
2055                         currentScanLine = tempScanLine[0 .. 1];
2056                         
2057                         //if (isPalette)
2058                             currentScanLine[0] = 0;
2059                         //else
2060                         //    currentScanLine[0] = filtersToApply[offsetY % filtersToApply.length];
2061                         
2062                         scanLineFilterMethodOffset = 0;
2063                         if (IHDR.interlaceMethod == PngIHDRInterlaceMethod.Adam7) {
2064                             if (offsetX > 0) {
2065                                 //scanLineFilterMethodOffset = 1;
2066                             }
2067                         }
2068                     } else
2069                         currentScanLine = tempScanLine[0 .. 0];
2070                     byteToOffset = 0; // 1, 2, 4 bit depth
2071                 }
2072                 
2073                 bool[size_t][size_t] doneSets;
2074 
2075                 if (IHDR.interlaceMethod == PngIHDRInterlaceMethod.Adam7) {
2076                     pass = 0;
2077                     while(pass < 7) {
2078                         offsetY = starting_row[pass];
2079 
2080                         while(offsetY < IHDR.height) {
2081                             offsetX = starting_col[pass];
2082                             startScanLine();
2083 
2084                             while(offsetX < IHDR.width) {
2085                                 assert(doneSets.get(offsetY, null).get(offsetX, true));
2086                                 doneSets[offsetY][offsetX] = true;
2087                                 serializeScanLine(value[offsetX, offsetY]);
2088                                 offsetX += col_increment[pass];
2089                             }
2090 
2091                             filterScanLine(currentScanLine);
2092                             offsetY += row_increment[pass];
2093                         }
2094                         
2095                         pass++;
2096                     }
2097                 } else if (IHDR.interlaceMethod == PngIHDRInterlaceMethod.NoInterlace) {
2098                     foreach(y; 0 .. IHDR.height) {
2099                         offsetY = y;
2100                         offsetX = 0;
2101                         startScanLine();
2102 
2103                         foreach(x; 0 .. IHDR.width) {
2104                             offsetX = x;
2105                             
2106                             assert(doneSets.get(offsetY, null).get(offsetX, true));
2107                                 doneSets[offsetY][offsetX] = true;
2108                             serializeScanLine(value[offsetX, offsetY]);
2109                         }
2110                         
2111                         filterScanLine(currentScanLine);
2112                     }
2113                 }
2114 
2115                 foreach(x; 0 .. IHDR.width) {
2116                     foreach(y; 0 .. IHDR.height) {
2117                         assert(doneSets.get(y, null).get(x, false));
2118                     }
2119                 }
2120                 
2121                 ubyte[] compressed;
2122 
2123                 // compress
2124                 if (IHDR.compressionMethod == PngIHDRCompresion.DeflateInflate) {
2125                     compressed = cast(ubyte[])compress(decompressed);
2126                 } else {
2127                     throw allocator.make!ImageNotLoadableException("IDAT unknown compression method");
2128                 }
2129 
2130                 ubyte[] bfr2 = buffer[0 .. compressed.length];
2131                 bfr2[] = compressed[];
2132 
2133                 theWriteFunc(cast(char[4])"IDAT", bfr2);
2134                 
2135                 alloc.dispose(tempFilterPrevious);
2136                 alloc.dispose(tempFilterCurrent);
2137                 alloc.dispose(decompressed);
2138             }
2139         }
2140         
2141         /*
2142          * Misc functions
2143          */
2144         
2145         void performCompatConfigure() {
2146             static if (!is(Color == HeadersOnly)) {
2147                 IHDR.width = cast(uint)value.width;
2148                 IHDR.height = cast(uint)value.height;
2149                 
2150                 // TODO: better color space guessing
2151                 
2152                 IHDR.bitDepth = PngIHDRBitDepth.BitDepth8;
2153                 IHDR.colorType = PngIHDRColorType.ColorUsed;
2154                 IHDR.compressionMethod = PngIHDRCompresion.DeflateInflate;
2155                 IHDR.filterMethod = PngIHDRFilter.Adaptive;
2156             }
2157         }
2158     }
2159 }
2160 
2161 /**
2162  * Loads a PNG file headers
2163  *
2164  * Can be used to determine which color type to use at runtime.
2165  *
2166  * Returns:
2167  *      A PNG files headers without the image data
2168  */
2169 managed!(PNGFileFormat!HeadersOnly) loadPNGHeaders(IR)(IR input, IAllocator allocator = theAllocator()) @trusted if (isInputRange!IR && is(ElementType!IR == ubyte)) {
2170 	managed!(PNGFileFormat!HeadersOnly) ret = managed!(PNGFileFormat!HeadersOnly)(managers(ReferenceCountedManager()), tuple(allocator), allocator);
2171 	ret.performInput(input);
2172     
2173     return ret;
2174 }
2175 
2176 /**
2177  * Loads a PNG file using specific color type
2178  *
2179  * Params:
2180  *      input       =   Input range that returns the files bytes
2181  *      allocator   =   The allocator to use the allocate the image
2182  *
2183  * Returns:
2184  *      A PNG file, loaded as an image along with its headers. Using specified image storage type.
2185  */
2186 managed!(PNGFileFormat!Color) loadPNG(Color, ImageImpl=ImageStorageHorizontal!Color, IR)(IR input, IAllocator allocator = theAllocator()) @trusted if (isInputRange!IR && is(ElementType!IR == ubyte) && isImage!ImageImpl) {
2187 	managed!(PNGFileFormat!Color) ret = managed!(PNGFileFormat!Color)(managers(), tuple(allocator), allocator);
2188 
2189     ret.theImageAllocator = &ret.allocateTheImage!ImageImpl;
2190     ret.performInput(input);
2191     
2192     return ret;
2193 }
2194 
2195 ///
2196 unittest {
2197     import std.experimental.color;
2198     import std.conv : hexString;
2199     
2200     import std.file : read;
2201 	auto input = cast(ubyte[])"89504E470D0A1A0A0000000D4948445200000002000000020802000000FDD49A73000000097048597300000B1300000B1301009A9C180000000774494D4507DF07150E122E54E72FF10000001974455874436F6D6D656E74004372656174656420776974682047494D5057810E17000000114944415408D763F8CFC0C0F09F014A000019F402FEE23BE1150000000049454E44AE426082".hexString;
2202     
2203     managed!(PNGFileFormat!RGB8) image = loadPNG!RGB8(input);
2204 }
2205 
2206 /**
2207  * Constructs a compatible version of an image as PNG
2208  * 
2209  * Params:
2210  *      form    =   The image to construct from
2211  *
2212  * Returns:
2213  *      A compatible PNG image
2214  */
2215 managed!(PNGFileFormat!Color) asPNG(From, Color = ImageColor!From, ImageImpl=ImageStorageHorizontal!Color)(From from, IAllocator allocator = theAllocator())
2216 if (isImage!From && !is(From == struct)) {
2217     import devisualization.image.primitives : copyTo;
2218     
2219     managed!(PNGFileFormat!Color) ret = managed!(PNGFileFormat!Color)(managers(), tuple(allocator), allocator);
2220     ret.allocateTheImage!ImageImpl(from.width, from.height);
2221     
2222     from.copyTo(ret.value);
2223     ret.performCompatConfigure();
2224     
2225     return ret;
2226 }
2227 
2228 managed!(PNGFileFormat!Color) asPNG(From, Color = ImageColor!From, ImageImpl=ImageStorageHorizontal!Color)(ref From from, IAllocator allocator = theAllocator()) @safe
2229 if (isImage!From && is(From == struct)) {
2230 	return asPNG!(From, Color, ImageImpl)(&from, allocator);
2231 }
2232 
2233 ///
2234 unittest {
2235     import std.experimental.color;
2236     import std.conv : hexString;
2237     
2238     import std.file : read;
2239 	auto input = cast(ubyte[])"89504E470D0A1A0A0000000D4948445200000002000000020802000000FDD49A73000000097048597300000B1300000B1301009A9C180000000774494D4507DF07150E122E54E72FF10000001974455874436F6D6D656E74004372656174656420776974682047494D5057810E17000000114944415408D763F8CFC0C0F09F014A000019F402FEE23BE1150000000049454E44AE426082".hexString;
2240 	
2241     ImageStorageHorizontal!RGB8 image = ImageStorageHorizontal!RGB8(2, 2);
2242     managed!(PNGFileFormat!RGB8) image2 = asPNG(&image);
2243     
2244     // modify some fields
2245     
2246     ubyte[] or = image2.toBytes();
2247 }
2248 
2249 ///
2250 enum PngTextKeywords : string {
2251     ///
2252     Title = "Title",
2253     ///
2254     Author = "Author",
2255     ///
2256     Description = "Description",
2257     ///
2258     Copyright = "Copyright",
2259     ///
2260     CreationTime = "Creation Time",
2261     ///
2262     Software = "Software",
2263     ///
2264     Disclaimer = "Disclaimer",
2265     ///
2266     Warning = "Warning",
2267     ///
2268     Source = "Source",
2269     ///
2270     Comment = "Comment"
2271 }
2272 
2273 ///
2274 enum PngIHDRColorType : ubyte {
2275     ///
2276     Grayscale = 0,                                          // valid (0)
2277     Palette = 1 << 0,                                       // not valid
2278     ///
2279     ColorUsed = 1 << 1,                                     // valid (2) rgb
2280     ///
2281     AlphaChannelUsed = 1 << 2,                              // valid (4) a
2282     ///
2283     PalletteWithColorUsed = Palette | ColorUsed,            // valid (1, 2) index + alpha
2284     ///
2285     ColorUsedWithAlpha = ColorUsed | AlphaChannelUsed,       // valid (2, 4) rgba
2286     ///
2287     GrayscaleWithAlpha = Grayscale | AlphaChannelUsed
2288 }
2289 
2290 ///
2291 enum PngIHDRBitDepth : ubyte {
2292     // valid with color type:
2293     ///    
2294     BitDepth1 = 1,                                          // 0, 3
2295     ///
2296     BitDepth2 = 2,                                          // 0, 3
2297     ///
2298     BitDepth4 = 4,                                          // 0, 3
2299     ///
2300     BitDepth8 = 8,                                          // 0, 2, 3, 4, 8
2301     ///
2302     BitDepth16 = 16                                         // 0, 2, 4, 8
2303 }
2304 
2305 ///
2306 enum PngIHDRCompresion : ubyte {
2307     ///
2308     DeflateInflate = 0
2309 }
2310 
2311 ///
2312 enum PngIHDRFilter : ubyte {
2313     ///
2314     Adaptive = 0
2315 }
2316 
2317 ///
2318 enum PngIHDRInterlaceMethod : ubyte {
2319     ///
2320     NoInterlace =  0,
2321     ///
2322     Adam7 = 1
2323 }
2324 
2325 ///
2326 enum PngRenderingIntent : ubyte {
2327     ///
2328     Perceptual = 0,
2329     ///
2330     RelativeColorimetric = 1,
2331     ///
2332     Saturation = 2,
2333     ///
2334     AbsoluteColorimetric = 3,
2335     ///
2336     Unknown=255
2337 }
2338 
2339 ///
2340 enum PngPhysicalPixelUnit : ubyte {
2341     ///
2342     Unknown = 0,
2343     ///
2344     Meter = 1
2345 }
2346 
2347 ///
2348 struct IHDR_Chunk {
2349     ///
2350     uint width;
2351     ///
2352     uint height;
2353     ///
2354     PngIHDRBitDepth bitDepth;
2355     ///
2356     PngIHDRColorType colorType;
2357     ///
2358     PngIHDRCompresion compressionMethod;
2359     ///
2360     PngIHDRFilter filterMethod;
2361     ///
2362     PngIHDRInterlaceMethod interlaceMethod;
2363 }
2364 
2365 ///
2366 struct PLTE_Chunk {
2367     ///
2368     alias Color = RGB8;
2369     
2370     ///
2371     Color[] colors;
2372 }
2373 
2374 ///
2375 struct cHRM_Chunk {    
2376     ///
2377     uint white_x;
2378     ///
2379     uint white_y;
2380     
2381     ///
2382     uint red_x;
2383     ///
2384     uint red_y;
2385     
2386     ///
2387     uint green_x;
2388     ///
2389     uint green_y;
2390     
2391     ///
2392     uint blue_x;
2393     ///
2394     uint blue_y;
2395 }
2396 
2397 ///
2398 struct sRGB_Chunk {
2399     ///
2400     PngRenderingIntent intent;
2401 }
2402 
2403 ///
2404 struct iCCP_Chunk {
2405     ///
2406     string profileName;
2407     
2408     ///
2409     PngIHDRCompresion compressionMethod;
2410     
2411     ///
2412     ubyte[] profile;
2413 }
2414 
2415 ///
2416 struct gAMA_Chunk {   
2417     /// 
2418     uint value;
2419     
2420     ///
2421     @property void set(float value) {
2422         this.value = cast(uint)(value * 100000);
2423     }
2424     
2425     ///
2426     @property float get() {
2427         return value / 100000f;
2428     }
2429 }
2430 
2431 ///
2432 union tRNS_Chunk {
2433     ///
2434     ubyte[] indexAlphas;
2435     ///
2436     RGB8 b8;
2437     ///
2438     RGB16 b16;
2439 }
2440 
2441 ///
2442 union bKGD_Chunk {
2443     ///
2444     ubyte index;
2445     ///
2446     RGB8 b8;
2447     ///
2448     RGB16 b16;
2449 }
2450 
2451 ///
2452 struct pPHs_Chunk {
2453     ///
2454     uint ppx;
2455     
2456     ///
2457     uint ppy;
2458     
2459     ///
2460     PngPhysicalPixelUnit unit;
2461 }
2462 
2463 ///
2464 union sBIT_Chunk {
2465     ///
2466     ubyte grayScale;
2467     ///
2468     ubyte[3] trueColor;
2469     ///
2470     ubyte[3] indexed;
2471     ///
2472     ubyte[2] grayScaleAlpha;
2473     ///
2474     ubyte[4] trueColorAlpha;
2475 }
2476 
2477 ///
2478 struct sPLT_Chunk {
2479     ///
2480     string paletteName;
2481     
2482     ///
2483     PngIHDRBitDepth sampleDepth;
2484     
2485     ///
2486     struct Entry {
2487         ///
2488         union Color {
2489             ///
2490             RGBA8 b8;
2491             ///
2492             RGBA16 b16;
2493         }
2494         
2495         ///
2496         Color color;
2497         
2498         ///
2499         ushort frequency;
2500     }
2501     
2502     ///
2503     Entry[] colors;
2504 }
2505 
2506 /// Grumbles @ManuEvans...
2507 alias RGBA16 = RGB!("rgba", ushort);
2508 /// Grumbles @ManuEvans...
2509 alias RGB16 = RGB!("rgb", ushort);
2510 
2511 private {
2512     struct IDAT_Chunk(Color) {
2513         ubyte[] data;
2514     }
2515     
2516     enum {
2517         starting_row = [
2518             0, 0, 4, 0, 2, 0, 1
2519         ],
2520         
2521         starting_col  = [
2522             0, 4, 0, 2, 0, 1, 0
2523         ],
2524         
2525         row_increment = [
2526             8, 8, 8, 4, 4, 2, 2
2527         ],
2528         
2529         col_increment = [
2530             8, 8, 4, 4, 2, 2, 1
2531         ],
2532         
2533         block_height = [
2534             8, 8, 4, 4, 2, 2, 1
2535         ],
2536         
2537         block_width = [
2538             8, 4, 4, 2, 2, 1, 1
2539         ]
2540     }
2541     
2542     ubyte PaethPredictor(ubyte a, ubyte b, ubyte c) {
2543         import std.math : abs;
2544         
2545         // a = left, b = above, c = upper left
2546         int p = a + b - c;        // initial estimate
2547         int pa = abs(p - a);      // distances to a, b, c
2548         int pb = abs(p - b);
2549         int pc = abs(p - c);
2550         
2551         // return nearest of a,b,c,
2552         // breaking ties in order a,b,c.
2553         if (pa <= pb && pa <= pc) return a;
2554         else if (pb <= pc) return b;
2555         else return c;
2556     }
2557 }